From 894ed83efcc02cf179862830261e0a458f254561 Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Sat, 5 Jun 2021 14:33:12 -0300 Subject: [PATCH 001/452] Add Schema Registry to the Kafka consumer --- .../nodes/Kafka/KafkaTrigger.node.ts | 34 +++++++++++++++++++ packages/nodes-base/package.json | 1 + packages/nodes-base/tsconfig.json | 1 + 3 files changed, 36 insertions(+) diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index 32f3b7a9c9..ffc48ceea1 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -5,6 +5,8 @@ import { SASLOptions, } from 'kafkajs'; +import { SchemaRegistry } from '@kafkajs/confluent-schema-registry'; + import { ITriggerFunctions, } from 'n8n-core'; @@ -98,6 +100,27 @@ export class KafkaTrigger implements INodeType { default: 30000, description: 'The time to await a response in ms.', }, + { + displayName: 'Use Schema Registry', + name: 'useSchemaRegistry', + type: 'boolean', + default: false, + description: 'Use Apache Avro serialization format and Confluent\' wire formats.', + }, + { + displayName: 'Schema Registry URL', + name: 'schemaRegistryUrl', + type: 'string', + displayOptions: { + show: { + useSchemaRegistry: [ + true, + ], + }, + }, + default: '', + description: 'URL of the schema registry.', + }, ], }, ], @@ -147,6 +170,11 @@ export class KafkaTrigger implements INodeType { const options = this.getNodeParameter('options', {}) as IDataObject; + let registry: SchemaRegistry; + if (options.useSchemaRegistry) { + registry = new SchemaRegistry({ host: options.schemaRegistryUrl as string }); + } + const startConsumer = async () => { await consumer.run({ eachMessage: async ({ topic, message }) => { @@ -160,6 +188,12 @@ export class KafkaTrigger implements INodeType { } catch (error) { } } + if (options.useSchemaRegistry) { + try { + value = await registry.decode(message.value as Buffer); + } catch (error) { } + } + data.message = value; data.topic = topic; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a52a1781e6..5780a74da4 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -607,6 +607,7 @@ "typescript": "~3.9.7" }, "dependencies": { + "@kafkajs/confluent-schema-registry": "1.0.6", "@types/lossless-json": "^1.0.0", "@types/promise-ftp": "^1.3.4", "@types/snowflake-sdk": "^1.5.1", diff --git a/packages/nodes-base/tsconfig.json b/packages/nodes-base/tsconfig.json index cbcbf98937..6edb1080a4 100644 --- a/packages/nodes-base/tsconfig.json +++ b/packages/nodes-base/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "lib": [ + "dom", "es2017", "es2019.array" ], From 4af53d1b951c81e3690d8619c305c73ed44e108d Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Sat, 5 Jun 2021 16:46:06 -0300 Subject: [PATCH 002/452] Add Schema Registry to the Kafka producer --- packages/nodes-base/nodes/Kafka/Kafka.node.ts | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Kafka/Kafka.node.ts b/packages/nodes-base/nodes/Kafka/Kafka.node.ts index 6270da679a..1763d1a85a 100644 --- a/packages/nodes-base/nodes/Kafka/Kafka.node.ts +++ b/packages/nodes-base/nodes/Kafka/Kafka.node.ts @@ -6,6 +6,8 @@ import { TopicMessages, } from 'kafkajs'; +import { SchemaRegistry } from '@kafkajs/confluent-schema-registry'; + import { IExecuteFunctions, } from 'n8n-core'; @@ -74,6 +76,43 @@ export class Kafka implements INodeType { type: 'boolean', default: false, }, + { + displayName: 'Use Schema Registry', + name: 'useSchemaRegistry', + type: 'boolean', + default: false, + description: 'Use Apache Avro serialization format and Confluent\' wire formats.', + }, + { + displayName: 'Schema Registry URL', + name: 'schemaRegistryUrl', + type: 'string', + required: true, + displayOptions: { + show: { + useSchemaRegistry: [ + true, + ], + }, + }, + default: '', + description: 'URL of the schema registry.', + }, + { + displayName: 'Event Name', + name: 'eventName', + type: 'string', + required: true, + displayOptions: { + show: { + useSchemaRegistry: [ + true, + ], + }, + }, + default: '', + description: 'Namespace and Name of Schema in Schema Registry (namespace.name).', + }, { displayName: 'Headers', name: 'headersUi', @@ -170,6 +209,8 @@ export class Kafka implements INodeType { const options = this.getNodeParameter('options', 0) as IDataObject; const sendInputData = this.getNodeParameter('sendInputData', 0) as boolean; + const useSchemaRegistry = this.getNodeParameter('useSchemaRegistry', 0) as boolean; + const timeout = options.timeout as number; let compression = CompressionTypes.None; @@ -211,7 +252,7 @@ export class Kafka implements INodeType { await producer.connect(); - let message: string; + let message: string | Buffer; for (let i = 0; i < length; i++) { if (sendInputData === true) { @@ -220,6 +261,18 @@ export class Kafka implements INodeType { message = this.getNodeParameter('message', i) as string; } + if (useSchemaRegistry) { + try { + const schemaRegistryUrl = this.getNodeParameter('schemaRegistryUrl', 0) as string; + const eventName = this.getNodeParameter('eventName', 0) as string; + + const registry = new SchemaRegistry({ host: schemaRegistryUrl }); + const id = await registry.getLatestSchemaId(eventName); + + message = await registry.encode(id, JSON.parse(message)); + } catch (error) {} + } + const topic = this.getNodeParameter('topic', i) as string; const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; From e5e09293326698db6613b477103a9c0dddf4e4df Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Sat, 5 Jun 2021 18:48:00 -0300 Subject: [PATCH 003/452] Move useSchemaRegistry out of the options to be easier to see the extra fields --- .../nodes/Kafka/KafkaTrigger.node.ts | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index ffc48ceea1..c2ab8db751 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -58,6 +58,28 @@ export class KafkaTrigger implements INodeType { placeholder: 'n8n-kafka', description: 'ID of the consumer group.', }, + { + displayName: 'Use Schema Registry', + name: 'useSchemaRegistry', + type: 'boolean', + default: false, + description: 'Use Apache Avro serialization format and Confluent\' wire formats.', + }, + { + displayName: 'Schema Registry URL', + name: 'schemaRegistryUrl', + type: 'string', + required: true, + displayOptions: { + show: { + useSchemaRegistry: [ + true, + ], + }, + }, + default: '', + description: 'URL of the schema registry.', + }, { displayName: 'Options', name: 'options', @@ -100,27 +122,6 @@ export class KafkaTrigger implements INodeType { default: 30000, description: 'The time to await a response in ms.', }, - { - displayName: 'Use Schema Registry', - name: 'useSchemaRegistry', - type: 'boolean', - default: false, - description: 'Use Apache Avro serialization format and Confluent\' wire formats.', - }, - { - displayName: 'Schema Registry URL', - name: 'schemaRegistryUrl', - type: 'string', - displayOptions: { - show: { - useSchemaRegistry: [ - true, - ], - }, - }, - default: '', - description: 'URL of the schema registry.', - }, ], }, ], @@ -170,9 +171,13 @@ export class KafkaTrigger implements INodeType { const options = this.getNodeParameter('options', {}) as IDataObject; + const useSchemaRegistry = this.getNodeParameter('useSchemaRegistry', 0) as boolean; + + const schemaRegistryUrl = this.getNodeParameter('schemaRegistryUrl', 0) as string; + let registry: SchemaRegistry; - if (options.useSchemaRegistry) { - registry = new SchemaRegistry({ host: options.schemaRegistryUrl as string }); + if (useSchemaRegistry) { + registry = new SchemaRegistry({ host: schemaRegistryUrl }); } const startConsumer = async () => { From 87b0e9992323dbd259d52f09199624b54aa06797 Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Sat, 5 Jun 2021 21:22:32 -0300 Subject: [PATCH 004/452] fix typo options.useSchemaRegistry -> useSchemaRegistry --- packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index c2ab8db751..c4587beca3 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -193,7 +193,7 @@ export class KafkaTrigger implements INodeType { } catch (error) { } } - if (options.useSchemaRegistry) { + if (useSchemaRegistry) { try { value = await registry.decode(message.value as Buffer); } catch (error) { } From 23b9e7bbe151e2edd9436619c19d3fd11f6d9b80 Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Sun, 27 Jun 2021 15:18:58 -0300 Subject: [PATCH 005/452] Add option to return the headers of the message --- .../nodes-base/nodes/Kafka/KafkaTrigger.node.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index c4587beca3..10a0034223 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -122,6 +122,13 @@ export class KafkaTrigger implements INodeType { default: 30000, description: 'The time to await a response in ms.', }, + { + displayName: 'Return headers', + name: 'returnHeaders', + type: 'boolean', + default: false, + description: 'Return the headers received from Kafka', + }, ], }, ], @@ -199,6 +206,16 @@ export class KafkaTrigger implements INodeType { } catch (error) { } } + if (options.returnHeaders) { + const headers: {[key: string]: string} = {}; + for (const key in message.headers) { + const header = message.headers[key]; + headers[key] = header?.toString('utf8') || ''; + } + + data.headers = headers; + } + data.message = value; data.topic = topic; From 882d1eca8e5087ddab3ce6ef0432f926fa7ac87e Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Fri, 2 Jul 2021 13:24:28 -0300 Subject: [PATCH 006/452] PR Suggestions --- packages/nodes-base/nodes/Kafka/Kafka.node.ts | 8 +++++--- packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts | 10 +++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/nodes-base/nodes/Kafka/Kafka.node.ts b/packages/nodes-base/nodes/Kafka/Kafka.node.ts index 1763d1a85a..473103d4ec 100644 --- a/packages/nodes-base/nodes/Kafka/Kafka.node.ts +++ b/packages/nodes-base/nodes/Kafka/Kafka.node.ts @@ -81,7 +81,7 @@ export class Kafka implements INodeType { name: 'useSchemaRegistry', type: 'boolean', default: false, - description: 'Use Apache Avro serialization format and Confluent\' wire formats.', + description: 'Use Confluent Schema Registry.', }, { displayName: 'Schema Registry URL', @@ -95,7 +95,7 @@ export class Kafka implements INodeType { ], }, }, - default: '', + default: 'https://schema-registry-domain:8081', description: 'URL of the schema registry.', }, { @@ -270,7 +270,9 @@ export class Kafka implements INodeType { const id = await registry.getLatestSchemaId(eventName); message = await registry.encode(id, JSON.parse(message)); - } catch (error) {} + } catch (exception) { + throw new NodeOperationError(this.getNode(), 'Verify your Schema Registry configuration'); + } } const topic = this.getNodeParameter('topic', i) as string; diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index 10a0034223..a6ffb37c96 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -63,7 +63,7 @@ export class KafkaTrigger implements INodeType { name: 'useSchemaRegistry', type: 'boolean', default: false, - description: 'Use Apache Avro serialization format and Confluent\' wire formats.', + description: 'Use Confluent Schema Registry.', }, { displayName: 'Schema Registry URL', @@ -77,7 +77,7 @@ export class KafkaTrigger implements INodeType { ], }, }, - default: '', + default: 'https://schema-registry-domain:8081', description: 'URL of the schema registry.', }, { @@ -182,11 +182,6 @@ export class KafkaTrigger implements INodeType { const schemaRegistryUrl = this.getNodeParameter('schemaRegistryUrl', 0) as string; - let registry: SchemaRegistry; - if (useSchemaRegistry) { - registry = new SchemaRegistry({ host: schemaRegistryUrl }); - } - const startConsumer = async () => { await consumer.run({ eachMessage: async ({ topic, message }) => { @@ -202,6 +197,7 @@ export class KafkaTrigger implements INodeType { if (useSchemaRegistry) { try { + const registry = new SchemaRegistry({ host: schemaRegistryUrl }); value = await registry.decode(message.value as Buffer); } catch (error) { } } From 7239e4f355aaa437aa6bb9a640024d57d2670e5d Mon Sep 17 00:00:00 2001 From: Ricardo Georgel Date: Mon, 5 Jul 2021 11:54:37 -0300 Subject: [PATCH 007/452] PR suggestions - add placeholder --- packages/nodes-base/nodes/Kafka/Kafka.node.ts | 3 ++- packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Kafka/Kafka.node.ts b/packages/nodes-base/nodes/Kafka/Kafka.node.ts index 473103d4ec..55bd5dc735 100644 --- a/packages/nodes-base/nodes/Kafka/Kafka.node.ts +++ b/packages/nodes-base/nodes/Kafka/Kafka.node.ts @@ -95,7 +95,8 @@ export class Kafka implements INodeType { ], }, }, - default: 'https://schema-registry-domain:8081', + placeholder: 'https://schema-registry-domain:8081', + default: '', description: 'URL of the schema registry.', }, { diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index a6ffb37c96..7640bcd7fc 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -77,7 +77,8 @@ export class KafkaTrigger implements INodeType { ], }, }, - default: 'https://schema-registry-domain:8081', + placeholder: 'https://schema-registry-domain:8081', + default: '', description: 'URL of the schema registry.', }, { From 49fbea75516a7ff6600c86f51aaaa9aff48c0387 Mon Sep 17 00:00:00 2001 From: Roberto Damiani Date: Sun, 10 Oct 2021 02:42:25 +0200 Subject: [PATCH 008/452] onedrive folders hierarchy creation support --- .../Microsoft/OneDrive/FolderDescription.ts | 2 +- .../OneDrive/MicrosoftOneDrive.node.ts | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts index 16f1ccdf4d..f910265bb0 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -67,7 +67,7 @@ export const folderFields = [ }, }, default: '', - description: `Folder's name`, + description: `Folder's name, or a folders slash (/) separated hierarchy`, }, { displayName: 'Options', diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index da16900725..62cc9e0494 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -209,17 +209,24 @@ export class MicrosoftOneDrive implements INodeType { if (resource === 'folder') { //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online if (operation === 'create') { - const name = this.getNodeParameter('name', i) as string; + const names = (this.getNodeParameter('name', i) as string).split("/").filter( s => s.trim() != "" ); const options = this.getNodeParameter('options', i) as IDataObject; - const body: IDataObject = { - name, - folder: {}, - }; - let endpoint = '/drive/root/children'; - if (options.parentFolderId) { - endpoint = `/drive/items/${options.parentFolderId}/children`; + let parentFolderId = options.parentFolderId ? options.parentFolderId : null; + for( let name of names ) { + let body: IDataObject = { + name, + folder: {}, + }; + let endpoint = '/drive/root/children'; + if (parentFolderId) { + endpoint = `/drive/items/${parentFolderId}/children`; + } + responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body); + if( !responseData.id ) { + break; + } + parentFolderId = responseData.id; } - responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body); returnData.push(responseData); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online From c888b0512ac81c8007df6fb0689ceb279c83199b Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 17 Oct 2021 09:28:29 -0400 Subject: [PATCH 009/452] :zap: Small change to #2305 --- .../Microsoft/OneDrive/MicrosoftOneDrive.node.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index 62cc9e0494..d71ce3fb9b 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -97,7 +97,7 @@ export class MicrosoftOneDrive implements INodeType { body.name = additionalFields.name as string; } responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${fileId}/copy`, body, {}, undefined, {}, { json: true, resolveWithFullResponse: true }); - responseData = { location : responseData.headers.location }; + responseData = { location: responseData.headers.location }; returnData.push(responseData as IDataObject); } //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online @@ -193,7 +193,7 @@ export class MicrosoftOneDrive implements INodeType { const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const body = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); - responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body, {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length }, {} ); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body, {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length }, {}); returnData.push(JSON.parse(responseData) as IDataObject); } else { @@ -201,7 +201,7 @@ export class MicrosoftOneDrive implements INodeType { if (fileName === '') { throw new NodeOperationError(this.getNode(), 'File name must be set!'); } - responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}:/content`, body, {}, undefined, { 'Content-Type': 'text/plain' }); returnData.push(responseData as IDataObject); } } @@ -209,11 +209,11 @@ export class MicrosoftOneDrive implements INodeType { if (resource === 'folder') { //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online if (operation === 'create') { - const names = (this.getNodeParameter('name', i) as string).split("/").filter( s => s.trim() != "" ); + const names = (this.getNodeParameter('name', i) as string).split('/').filter(s => s.trim() !== ''); const options = this.getNodeParameter('options', i) as IDataObject; let parentFolderId = options.parentFolderId ? options.parentFolderId : null; - for( let name of names ) { - let body: IDataObject = { + for (const name of names) { + const body: IDataObject = { name, folder: {}, }; @@ -222,7 +222,7 @@ export class MicrosoftOneDrive implements INodeType { endpoint = `/drive/items/${parentFolderId}/children`; } responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body); - if( !responseData.id ) { + if (!responseData.id) { break; } parentFolderId = responseData.id; From 095ce93cf774528b264bb57a654ef302c84a32b0 Mon Sep 17 00:00:00 2001 From: pemontto Date: Sun, 17 Oct 2021 17:48:24 +0100 Subject: [PATCH 010/452] =?UTF-8?q?=E2=9C=A8=20Add=20loadOptions=20for=20H?= =?UTF-8?q?ome=20Assistant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HomeAssistant/CameraProxyDescription.ts | 5 +- .../nodes/HomeAssistant/GenericFunctions.ts | 49 ++++++++++++++++++- .../nodes/HomeAssistant/HomeAssistant.node.ts | 26 ++++++++++ .../nodes/HomeAssistant/ServiceDescription.ts | 13 ++++- .../nodes/HomeAssistant/StateDescription.ts | 10 +++- 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts b/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts index 35764bfc2a..3f28c6dc9a 100644 --- a/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts +++ b/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts @@ -33,7 +33,10 @@ export const cameraProxyFields = [ { displayName: 'Camera Entity ID', name: 'cameraEntityId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCameraEntities', + }, default: '', required: true, displayOptions: { diff --git a/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts b/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts index 796b03ee51..673caab7c3 100644 --- a/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts @@ -4,15 +4,17 @@ import { import { IExecuteFunctions, + ILoadOptionsFunctions, } from 'n8n-core'; import { IDataObject, + INodePropertyOptions, NodeApiError, NodeOperationError, } from 'n8n-workflow'; -export async function homeAssistantApiRequest(this: IExecuteFunctions, method: string, resource: string, body: IDataObject = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}) { +export async function homeAssistantApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: IDataObject = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}) { const credentials = await this.getCredentials('homeAssistantApi'); if (credentials === undefined) { @@ -35,8 +37,51 @@ export async function homeAssistantApiRequest(this: IExecuteFunctions, method: s delete options.body; } try { - return await this.helpers.request(options); + if (this.helpers.request) { + return await this.helpers.request(options); + } } catch (error) { throw new NodeApiError(this.getNode(), error); } } + +export async function getHomeAssistantEntities(this: IExecuteFunctions | ILoadOptionsFunctions, domain = '') { + const returnData: INodePropertyOptions[] = []; + const entities = await homeAssistantApiRequest.call(this, 'GET', '/states'); + for (const entity of entities) { + const entityId = entity.entity_id as string; + if (domain === '' || domain && entityId.startsWith(domain)) { + const entityName = entity.attributes.friendly_name as string || entityId; + returnData.push({ + name: entityName, + value: entityId, + }); + } + } + return returnData; +} + +export async function getHomeAssistantServices(this: IExecuteFunctions | ILoadOptionsFunctions, domain = '') { + const returnData: INodePropertyOptions[] = []; + const services = await homeAssistantApiRequest.call(this, 'GET', '/services'); + if (domain === '') { + // If no domain specified return domains + const domains = services.map(({ domain }: IDataObject) => domain as string).sort(); + returnData.push(...domains.map((service: string) => ({ name: service, value: service }))); + return returnData; + } else { + // If we have a domain, return all relevant services + const domainServices = services.filter((service: IDataObject) => service.domain === domain); + for (const domainService of domainServices) { + for (const [serviceID, value] of Object.entries(domainService.services)) { + const serviceProperties = value as IDataObject; + const serviceName = serviceProperties.description || serviceID; + returnData.push({ + name: serviceName as string, + value: serviceID, + }); + } + } + } + return returnData; +} diff --git a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts index d84525f159..008ebf5680 100644 --- a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts +++ b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts @@ -4,7 +4,9 @@ import { import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodePropertyOptions, INodeType, INodeTypeDescription, } from 'n8n-workflow'; @@ -49,6 +51,8 @@ import { } from './CameraProxyDescription'; import { + getHomeAssistantEntities, + getHomeAssistantServices, homeAssistantApiRequest, } from './GenericFunctions'; @@ -133,6 +137,28 @@ export class HomeAssistant implements INodeType { ], }; + methods = { + loadOptions: { + async getAllEntities(this: ILoadOptionsFunctions): Promise { + return await getHomeAssistantEntities.call(this); + }, + async getCameraEntities(this: ILoadOptionsFunctions): Promise { + return await getHomeAssistantEntities.call(this, 'camera'); + }, + async getDomains(this: ILoadOptionsFunctions): Promise { + return await getHomeAssistantServices.call(this); + }, + async getDomainServices(this: ILoadOptionsFunctions): Promise { + const currentDomain = this.getCurrentNodeParameter('domain') as string; + if (currentDomain) { + return await getHomeAssistantServices.call(this, currentDomain); + } else { + return []; + } + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; diff --git a/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts b/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts index d278d47227..100bbff1fb 100644 --- a/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts +++ b/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts @@ -83,7 +83,10 @@ export const serviceFields = [ { displayName: 'Domain', name: 'domain', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getDomains', + }, default: '', required: true, displayOptions: { @@ -100,7 +103,13 @@ export const serviceFields = [ { displayName: 'Service', name: 'service', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsDependsOn: [ + 'domain', + ], + loadOptionsMethod: 'getDomainServices', + }, default: '', required: true, displayOptions: { diff --git a/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts b/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts index aa356f1c7a..580e1fa292 100644 --- a/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts +++ b/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts @@ -43,7 +43,10 @@ export const stateFields = [ { displayName: 'Entity ID', name: 'entityId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAllEntities', + }, displayOptions: { show: { operation: [ @@ -110,7 +113,10 @@ export const stateFields = [ { displayName: 'Entity ID', name: 'entityId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAllEntities', + }, displayOptions: { show: { operation: [ From ff0fcfc511f0d990a200ec5cc6ce775bc73ee8a6 Mon Sep 17 00:00:00 2001 From: pemontto Date: Wed, 20 Oct 2021 15:24:54 +0100 Subject: [PATCH 011/452] =?UTF-8?q?=F0=9F=90=9B=20Fix=20custom=20fields=20?= =?UTF-8?q?for=20Jira=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/nodes-base/nodes/Jira/Jira.node.ts | 49 ++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 5dcd27e984..2123703ad6 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -1,3 +1,7 @@ +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, } from 'n8n-core'; @@ -5,12 +9,15 @@ import { import { IBinaryData, IBinaryKeyData, + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription, + NodeCredentialTestResult, NodeOperationError, } from 'n8n-workflow'; @@ -74,6 +81,7 @@ export class Jira implements INodeType { ], }, }, + testedBy: 'jiraSoftwareApiTest', }, { name: 'jiraSoftwareServerApi', @@ -85,6 +93,7 @@ export class Jira implements INodeType { ], }, }, + testedBy: 'jiraSoftwareApiTest', }, ], properties: [ @@ -145,6 +154,44 @@ export class Jira implements INodeType { }; methods = { + credentialTest: { + async jiraSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + let data; let domain; + const credentials = credential.data; + domain = credentials!.domain; + data = Buffer.from(`${credentials!.email}:${credentials!.password || credentials!.apiToken}`).toString('base64'); + + const endpoint = '/api/2/project'; + + const options: OptionsWithUri = { + headers: { + Authorization: `Basic ${data}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-Atlassian-Token': 'no-check', + }, + method: 'GET', + uri: `${domain}/rest${endpoint}`, + qs: { + recent: 0, + }, + json: true, + timeout: 5000, + }; + try { + const response = await this.helpers.request!(options); + } catch (err) { + return { + status: 'Error', + message: `Connection details not valid; ${err.message}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, loadOptions: { // Get all the projects to display them to user so that he can // select them easily @@ -370,7 +417,7 @@ export class Jira implements INodeType { } const res = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/createmeta?projectIds=${projectId}&issueTypeIds=${issueTypeId}&expand=projects.issuetypes.fields`, 'GET'); - + // tslint:disable-next-line: no-any const fields = res.projects.find((o: any) => o.id === projectId).issuetypes.find((o: any) => o.id === issueTypeId).fields; for (const key of Object.keys(fields)) { From 2f7ad6968ee7e4666b4059ef517eea29ac340757 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 20 Oct 2021 20:49:07 -0500 Subject: [PATCH 012/452] :zap: Fix credential overwrite via environment variables --- packages/cli/commands/start.ts | 8 ++++---- packages/cli/src/CredentialTypes.ts | 20 +------------------- packages/cli/src/CredentialsOverwrites.ts | 4 ++++ packages/cli/src/Server.ts | 22 ++-------------------- 4 files changed, 11 insertions(+), 43 deletions(-) diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 39e2786c02..3ecbddc0e7 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -183,10 +183,6 @@ export class Start extends Command { const loadNodesAndCredentials = LoadNodesAndCredentials(); await loadNodesAndCredentials.init(); - // Load the credentials overwrites if any exist - const credentialsOverwrites = CredentialsOverwrites(); - await credentialsOverwrites.init(); - // Load all external hooks const externalHooks = ExternalHooks(); await externalHooks.init(); @@ -197,6 +193,10 @@ export class Start extends Command { const credentialTypes = CredentialTypes(); await credentialTypes.init(loadNodesAndCredentials.credentialTypes); + // Load the credentials overwrites if any exist + const credentialsOverwrites = CredentialsOverwrites(); + await credentialsOverwrites.init(); + // Wait till the database is ready await startDbInitPromise; diff --git a/packages/cli/src/CredentialTypes.ts b/packages/cli/src/CredentialTypes.ts index f9c3504377..0d35381054 100644 --- a/packages/cli/src/CredentialTypes.ts +++ b/packages/cli/src/CredentialTypes.ts @@ -1,31 +1,13 @@ import { ICredentialType, ICredentialTypes as ICredentialTypesInterface } from 'n8n-workflow'; // eslint-disable-next-line import/no-cycle -import { CredentialsOverwrites, ICredentialsTypeData } from '.'; +import { ICredentialsTypeData } from '.'; class CredentialTypesClass implements ICredentialTypesInterface { credentialTypes: ICredentialsTypeData = {}; async init(credentialTypes: ICredentialsTypeData): Promise { this.credentialTypes = credentialTypes; - - // Load the credentials overwrites if any exist - const credentialsOverwrites = CredentialsOverwrites().getAll(); - - // eslint-disable-next-line no-restricted-syntax - for (const credentialType of Object.keys(credentialsOverwrites)) { - if (credentialTypes[credentialType] === undefined) { - // eslint-disable-next-line no-continue - continue; - } - - // Add which properties got overwritten that the Editor-UI knows - // which properties it should hide - // eslint-disable-next-line no-underscore-dangle, no-param-reassign - credentialTypes[credentialType].__overwrittenProperties = Object.keys( - credentialsOverwrites[credentialType], - ); - } } getAll(): ICredentialType[] { diff --git a/packages/cli/src/CredentialsOverwrites.ts b/packages/cli/src/CredentialsOverwrites.ts index 4fbdbf3e61..077d08eb14 100644 --- a/packages/cli/src/CredentialsOverwrites.ts +++ b/packages/cli/src/CredentialsOverwrites.ts @@ -12,6 +12,9 @@ class CredentialsOverwritesClass { private resolvedTypes: string[] = []; async init(overwriteData?: ICredentialsOverwrite) { + // If data gets reinitialized reset the resolved types cache + this.resolvedTypes.length = 0; + if (overwriteData !== undefined) { // If data is already given it can directly be set instead of // loaded from environment @@ -41,6 +44,7 @@ class CredentialsOverwritesClass { if (overwrites && Object.keys(overwrites).length) { this.overwriteData[type] = overwrites; + credentialTypeData.__overwrittenProperties = Object.keys(overwrites); } } } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index aead6ad68f..3532ad9c4b 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -27,16 +27,7 @@ import * as express from 'express'; import { readFileSync } from 'fs'; import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path'; -import { - FindManyOptions, - FindOneOptions, - getConnectionManager, - In, - IsNull, - LessThanOrEqual, - Like, - Not, -} from 'typeorm'; +import { FindManyOptions, getConnectionManager, In, IsNull, LessThanOrEqual, Not } from 'typeorm'; import * as bodyParser from 'body-parser'; import * as history from 'connect-history-api-fallback'; import * as os from 'os'; @@ -47,7 +38,7 @@ import * as clientOAuth1 from 'oauth-1.0a'; import { RequestOptions } from 'oauth-1.0a'; import * as csrf from 'csrf'; import * as requestPromise from 'request-promise-native'; -import { createHash, createHmac } from 'crypto'; +import { createHmac } from 'crypto'; // IMPORTANT! Do not switch to anther bcrypt library unless really necessary and // tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... import { compare } from 'bcryptjs'; @@ -73,9 +64,7 @@ import { INodeType, INodeTypeDescription, INodeTypeNameVersion, - IRunData, INodeVersionedType, - ITelemetryClientConfig, ITelemetrySettings, IWorkflowBase, IWorkflowCredentials, @@ -134,7 +123,6 @@ import { IWorkflowExecutionDataProcess, IWorkflowResponse, IPersonalizationSurveyAnswers, - LoadNodesAndCredentials, NodeTypes, Push, ResponseHelper, @@ -2795,16 +2783,10 @@ class App { return; } - const loadNodesAndCredentials = LoadNodesAndCredentials(); - const credentialsOverwrites = CredentialsOverwrites(); await credentialsOverwrites.init(body); - const credentialTypes = CredentialTypes(); - - await credentialTypes.init(loadNodesAndCredentials.credentialTypes); - this.presetCredentialsLoaded = true; ResponseHelper.sendSuccessResponse(res, { success: true }, true, 200); From 467ecdbe4cbbf0e4879d3b6c1859b49990a16957 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 20 Oct 2021 22:24:10 -0400 Subject: [PATCH 013/452] :zap: Improve description --- .../nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts index f910265bb0..471faa2abf 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -56,6 +56,7 @@ export const folderFields = [ name: 'name', required: true, type: 'string', + placeholder: '/root/folder', displayOptions: { show: { operation: [ @@ -67,7 +68,7 @@ export const folderFields = [ }, }, default: '', - description: `Folder's name, or a folders slash (/) separated hierarchy`, + description: 'The name or path of the folder', }, { displayName: 'Options', From 2c278bf8e2af3ad763a54eb06e677310ad6ecc1e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 20 Oct 2021 22:07:33 -0500 Subject: [PATCH 014/452] :zap: Rename placeholder --- .../nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts index 471faa2abf..2c6f8b5190 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -56,7 +56,7 @@ export const folderFields = [ name: 'name', required: true, type: 'string', - placeholder: '/root/folder', + placeholder: '/Pictures/2021', displayOptions: { show: { operation: [ From 2e49c4c1cfaf2464ab91b9d675a0467365a69947 Mon Sep 17 00:00:00 2001 From: pemontto <939704+pemontto@users.noreply.github.com> Date: Thu, 21 Oct 2021 04:14:58 +0100 Subject: [PATCH 015/452] :bug: Fix custom fields for Jira Server (#2347) --- packages/nodes-base/nodes/Jira/Jira.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 5dcd27e984..6c80792261 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -378,7 +378,7 @@ export class Jira implements INodeType { if (field.schema && Object.keys(field.schema).includes('customId')) { returnData.push({ name: field.name, - value: field.key, + value: field.key || field.fieldId, }); } } From 1d8e800620f5c57eb47dfb7501ad2402f101d604 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 20 Oct 2021 22:24:03 -0500 Subject: [PATCH 016/452] :zap: Simplify --- packages/nodes-base/nodes/Jira/Jira.node.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 7a489368b1..3cd0509566 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -156,12 +156,8 @@ export class Jira implements INodeType { methods = { credentialTest: { async jiraSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { - let data; let domain; const credentials = credential.data; - domain = credentials!.domain; - data = Buffer.from(`${credentials!.email}:${credentials!.password || credentials!.apiToken}`).toString('base64'); - - const endpoint = '/api/2/project'; + const data = Buffer.from(`${credentials!.email}:${credentials!.password || credentials!.apiToken}`).toString('base64'); const options: OptionsWithUri = { headers: { @@ -171,7 +167,7 @@ export class Jira implements INodeType { 'X-Atlassian-Token': 'no-check', }, method: 'GET', - uri: `${domain}/rest${endpoint}`, + uri: `${credentials!.domain}/rest/api/2/project`, qs: { recent: 0, }, @@ -179,7 +175,7 @@ export class Jira implements INodeType { timeout: 5000, }; try { - const response = await this.helpers.request!(options); + await this.helpers.request!(options); } catch (err) { return { status: 'Error', From ba647fbc7b5a5a64c12d9162436d9143a3117496 Mon Sep 17 00:00:00 2001 From: pemontto <939704+pemontto@users.noreply.github.com> Date: Thu, 21 Oct 2021 04:35:51 +0100 Subject: [PATCH 017/452] :zap: Add more auth tests (#2265) --- .../nodes/Bitbucket/BitbucketTrigger.node.ts | 41 ++++++++++++++++++ .../nodes-base/nodes/Github/Github.node.ts | 43 +++++++++++++++++++ .../nodes/HomeAssistant/HomeAssistant.node.ts | 41 ++++++++++++++++++ 3 files changed, 125 insertions(+) diff --git a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts index 96da977c40..665a7b1053 100644 --- a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts +++ b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts @@ -1,15 +1,20 @@ +import { OptionsWithUri } from 'request'; + import { IHookFunctions, IWebhookFunctions, } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, ILoadOptionsFunctions, INodePropertyOptions, INodeType, INodeTypeDescription, IWebhookResponseData, + NodeCredentialTestResult, } from 'n8n-workflow'; import { @@ -35,6 +40,7 @@ export class BitbucketTrigger implements INodeType { { name: 'bitbucketApi', required: true, + testedBy: 'bitbucketApiTest', }, ], webhooks: [ @@ -166,6 +172,41 @@ export class BitbucketTrigger implements INodeType { }; methods = { + credentialTest: { + async bitbucketApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + + const options: OptionsWithUri = { + method: 'GET', + auth: { + user: credentials!.username as string, + password: credentials!.appPassword as string, + }, + uri: 'https://api.bitbucket.org/2.0/user', + json: true, + timeout: 5000, + }; + + try { + const response = await this.helpers.request(options); + if (!response.username) { + return { + status: 'Error', + message: `Token is not valid: ${response.error}`, + }; + } + } catch (error) { + return { + status: 'Error', + message: `Settings are not valid: ${error}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, loadOptions: { // Get all the events to display them to user so that he can // select them easily diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 8b2564d8b3..d9ec2640a8 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -1,12 +1,17 @@ +import { OptionsWithUri } from 'request'; + import { IExecuteFunctions, } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, + NodeCredentialTestResult, NodeOperationError, } from 'n8n-workflow'; @@ -39,6 +44,7 @@ export class Github implements INodeType { { name: 'githubApi', required: true, + testedBy: 'githubApiTest', displayOptions: { show: { authentication: [ @@ -1698,6 +1704,43 @@ export class Github implements INodeType { ], }; + methods = { + credentialTest: { + async githubApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + const baseUrl = credentials!.server as string || 'https://api.github.com/user'; + + const options: OptionsWithUri = { + method: 'GET', + headers: { + 'User-Agent': 'n8n', + Authorization: `token ${credentials!.accessToken}`, + }, + uri: baseUrl, + json: true, + timeout: 5000, + }; + try { + const response = await this.helpers.request(options); + if (!response.id) { + return { + status: 'Error', + message: `Token is not valid: ${response.error}`, + }; + } + } catch (error) { + return { + status: 'Error', + message: `Settings are not valid: ${error}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, + }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); diff --git a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts index d84525f159..1a9ef184b2 100644 --- a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts +++ b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts @@ -3,10 +3,13 @@ import { } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, + NodeCredentialTestResult, } from 'n8n-workflow'; import { @@ -71,6 +74,7 @@ export class HomeAssistant implements INodeType { { name: 'homeAssistantApi', required: true, + testedBy: 'homeAssistantApiTest', }, ], properties: [ @@ -133,6 +137,43 @@ export class HomeAssistant implements INodeType { ], }; + methods = { + credentialTest: { + async homeAssistantApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + const options = { + method: 'GET', + headers: { + Authorization: `Bearer ${credentials!.accessToken}`, + }, + uri: `${credentials!.ssl === true ? 'https' : 'http'}://${credentials!.host}:${ credentials!.port || '8123' }/api/`, + json: true, + timeout: 5000, + }; + try { + const response = await this.helpers.request(options); + if (!response.message) { + return { + status: 'Error', + message: `Token is not valid: ${response.error}`, + }; + } + } catch (error) { + return { + status: 'Error', + message: `${error.statusCode === 401 ? 'Token is' : 'Settings are'} not valid: ${error}`, + }; + } + + return { + status: 'OK', + message: 'Authentication successful!', + }; + + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; From d0403dd87548702b8cdc384902ae2a8c4449bd07 Mon Sep 17 00:00:00 2001 From: Ahsan Virani Date: Thu, 21 Oct 2021 19:50:38 +0200 Subject: [PATCH 018/452] :zap: timeout process stop events (#2349) --- packages/cli/src/Interfaces.ts | 2 +- packages/cli/src/InternalHooks.ts | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 273f68dad0..d5c11a8f3f 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -308,7 +308,7 @@ export interface IDiagnosticInfo { export interface IInternalHooksClass { onN8nStop(): Promise; - onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise; + onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise; onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise; onWorkflowCreated(workflow: IWorkflowBase): Promise; onWorkflowDeleted(workflowId: string): Promise; diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index a92eec6160..4f6d4839d6 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -11,7 +11,7 @@ import { Telemetry } from './telemetry'; export class InternalHooksClass implements IInternalHooksClass { constructor(private telemetry: Telemetry) {} - async onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise { + async onServerStarted(diagnosticInfo: IDiagnosticInfo): Promise { const info = { version_cli: diagnosticInfo.versionCli, db_type: diagnosticInfo.databaseType, @@ -22,12 +22,15 @@ export class InternalHooksClass implements IInternalHooksClass { execution_variables: diagnosticInfo.executionVariables, n8n_deployment_type: diagnosticInfo.deploymentType, }; - await this.telemetry.identify(info); - await this.telemetry.track('Instance started', info); + + return Promise.all([ + this.telemetry.identify(info), + this.telemetry.track('Instance started', info), + ]); } async onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise { - await this.telemetry.track('User responded to personalization questions', { + return this.telemetry.track('User responded to personalization questions', { company_size: answers.companySize, coding_skill: answers.codingSkill, work_area: answers.workArea, @@ -36,20 +39,20 @@ export class InternalHooksClass implements IInternalHooksClass { } async onWorkflowCreated(workflow: IWorkflowBase): Promise { - await this.telemetry.track('User created workflow', { + return this.telemetry.track('User created workflow', { workflow_id: workflow.id, node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph, }); } async onWorkflowDeleted(workflowId: string): Promise { - await this.telemetry.track('User deleted workflow', { + return this.telemetry.track('User deleted workflow', { workflow_id: workflowId, }); } async onWorkflowSaved(workflow: IWorkflowBase): Promise { - await this.telemetry.track('User saved workflow', { + return this.telemetry.track('User saved workflow', { workflow_id: workflow.id, node_graph: TelemetryHelpers.generateNodesGraph(workflow).nodeGraph, }); @@ -96,10 +99,16 @@ export class InternalHooksClass implements IInternalHooksClass { } } - void this.telemetry.trackWorkflowExecution(properties); + return this.telemetry.trackWorkflowExecution(properties); } async onN8nStop(): Promise { - await this.telemetry.trackN8nStop(); + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 3000); + }); + + return Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]); } } From 029b9738eba4121c99b581dae4186016e0bd29e4 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 21 Oct 2021 14:20:24 -0400 Subject: [PATCH 019/452] :sparkles: Add Google Drive Trigger (#2219) * :sparkles: Google Drive Trigger * :zap: Small change * :zap: Small change * :zap: Improvements * :zap: Improvement * :zap: Improvements * :zap: Improvements * :zap: Improvements --- .../nodes/Google/Drive/GenericFunctions.ts | 32 +- .../Google/Drive/GoogleDriveTrigger.node.ts | 732 +++++++++++------- packages/nodes-base/package.json | 1 + 3 files changed, 464 insertions(+), 301 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts index 726b533655..185e00da2b 100644 --- a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts @@ -9,14 +9,17 @@ import { } from 'n8n-core'; import { - IDataObject, NodeApiError, NodeOperationError, + IDataObject, + IPollFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; import * as jwt from 'jsonwebtoken'; -export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string; let options: OptionsWithUri = { @@ -29,7 +32,9 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF uri: uri || `https://www.googleapis.com${resource}`, json: true, }; + options = Object.assign({}, options, option); + try { if (Object.keys(body).length === 0) { delete options.body; @@ -59,17 +64,16 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF } } -export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; let responseData; - query.maxResults = 100; - query.pageSize = 100; + query.maxResults = query.maxResults || 100; + query.pageSize = query.pageSize || 100; do { responseData = await googleApiRequest.call(this, method, endpoint, body, query); - query.pageToken = responseData['nextPageToken']; returnData.push.apply(returnData, responseData[propertyName]); } while ( responseData['nextPageToken'] !== undefined && @@ -79,7 +83,7 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp return returnData; } -function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise { +function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, credentials: IDataObject): Promise { //https://developers.google.com/identity/protocols/oauth2/service-account#httprest const scopes = [ @@ -125,3 +129,17 @@ function getAccessToken(this: IExecuteFunctions | IExecuteSingleFunctions | ILoa return this.helpers.request!(options); } + +export function extractId(url: string): string { + if (url.includes('/d/')) { + //https://docs.google.com/document/d/1TUJGUf5HUv9e6MJBzcOsPruxXDeGMnGYTBWfkMagcg4/edit + const data = url.match(/[-\w]{25,}/); + if (Array.isArray(data)) { + return data[0]; + } + } else if (url.includes('/folders/')) { + //https://drive.google.com/drive/u/0/folders/19MqnruIXju5sAWYD3J71im1d2CBJkZzy + return url.split('/folders/')[1]; + } + return url; +} diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts index c917e64874..a735df0401 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts @@ -1,294 +1,438 @@ -// import { google } from 'googleapis'; - -// import { -// IHookFunctions, -// IWebhookFunctions, -// } from 'n8n-core'; - -// import { -// IDataObject, -// INodeTypeDescription, -// INodeType, -// IWebhookResponseData, -// NodeOperationError, -// } from 'n8n-workflow'; - -// import { getAuthenticationClient } from './GoogleApi'; - - -// export class GoogleDriveTrigger implements INodeType { -// description: INodeTypeDescription = { -// displayName: 'Google Drive Trigger', -// name: 'googleDriveTrigger', -// icon: 'file:googleDrive.png', -// group: ['trigger'], -// version: 1, -// subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}', -// description: 'Starts the workflow when a file on Google Drive is changed', -// defaults: { -// name: 'Google Drive Trigger', -// color: '#3f87f2', -// }, -// inputs: [], -// outputs: ['main'], -// credentials: [ -// { -// name: 'googleApi', -// required: true, -// } -// ], -// webhooks: [ -// { -// name: 'default', -// httpMethod: 'POST', -// responseMode: 'onReceived', -// path: 'webhook', -// }, -// ], -// properties: [ -// { -// displayName: 'Resource Id', -// name: 'resourceId', -// type: 'string', -// default: '', -// required: true, -// placeholder: '', -// description: 'ID of the resource to watch, for example a file ID.', -// }, -// ], -// }; - -// // @ts-ignore (because of request) -// webhookMethods = { -// default: { -// async checkExists(this: IHookFunctions): Promise { -// // const webhookData = this.getWorkflowStaticData('node'); - -// // if (webhookData.webhookId === undefined) { -// // // No webhook id is set so no webhook can exist -// // return false; -// // } - -// // // Webhook got created before so check if it still exists -// // const owner = this.getNodeParameter('owner') as string; -// // const repository = this.getNodeParameter('repository') as string; -// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; - -// // try { -// // await githubApiRequest.call(this, 'GET', endpoint, {}); -// // } catch (error) { -// // if (error.message.includes('[404]:')) { -// // // Webhook does not exist -// // delete webhookData.webhookId; -// // delete webhookData.webhookEvents; - -// // return false; -// // } - -// // // Some error occured -// // throw e; -// // } - -// // If it did not error then the webhook exists -// // return true; -// return false; -// }, -// async create(this: IHookFunctions): Promise { -// const webhookUrl = this.getNodeWebhookUrl('default'); - -// const resourceId = this.getNodeParameter('resourceId') as string; - -// const credentials = await this.getCredentials('googleApi'); - -// if (credentials === undefined) { -// throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); -// } - -// const scopes = [ -// 'https://www.googleapis.com/auth/drive', -// 'https://www.googleapis.com/auth/drive.appdata', -// 'https://www.googleapis.com/auth/drive.photos.readonly', -// ]; - -// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); - -// const drive = google.drive({ -// version: 'v3', -// auth: client, -// }); - - -// const accessToken = await client.getAccessToken(); -// console.log('accessToken: '); -// console.log(accessToken); - -// const asdf = await drive.changes.getStartPageToken(); -// // console.log('asdf: '); -// // console.log(asdf); - - - - -// const response = await drive.changes.watch({ -// // -// pageToken: asdf.data.startPageToken, -// requestBody: { -// id: 'asdf-test-2', -// address: webhookUrl, -// resourceId, -// type: 'web_hook', -// // page_token: '', -// } -// }); - -// console.log('...response...CREATE'); -// console.log(JSON.stringify(response, null, 2)); - - - - - -// // const endpoint = `/repos/${owner}/${repository}/hooks`; - -// // const body = { -// // name: 'web', -// // config: { -// // url: webhookUrl, -// // content_type: 'json', -// // // secret: '...later...', -// // insecure_ssl: '1', // '0' -> not allow inscure ssl | '1' -> allow insercure SSL -// // }, -// // events, -// // active: true, -// // }; - - -// // let responseData; -// // try { -// // responseData = await githubApiRequest.call(this, 'POST', endpoint, body); -// // } catch (error) { -// // if (error.message.includes('[422]:')) { -// // throw new NodeOperationError(this.getNode(), 'A webhook with the identical URL exists already. Please delete it manually on Github!'); -// // } - -// // throw e; -// // } - -// // if (responseData.id === undefined || responseData.active !== true) { -// // // Required data is missing so was not successful -// // throw new NodeOperationError(this.getNode(), 'Github webhook creation response did not contain the expected data.'); -// // } - -// // const webhookData = this.getWorkflowStaticData('node'); -// // webhookData.webhookId = responseData.id as string; -// // webhookData.webhookEvents = responseData.events as string[]; - -// return true; -// }, -// async delete(this: IHookFunctions): Promise { -// const webhookUrl = this.getNodeWebhookUrl('default'); - -// const resourceId = this.getNodeParameter('resourceId') as string; - -// const credentials = await this.getCredentials('googleApi'); - -// if (credentials === undefined) { -// throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); -// } - -// const scopes = [ -// 'https://www.googleapis.com/auth/drive', -// 'https://www.googleapis.com/auth/drive.appdata', -// 'https://www.googleapis.com/auth/drive.photos.readonly', -// ]; - -// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); - -// const drive = google.drive({ -// version: 'v3', -// auth: client, -// }); - -// // Remove channel -// const response = await drive.channels.stop({ -// requestBody: { -// id: 'asdf-test-2', -// address: webhookUrl, -// resourceId, -// type: 'web_hook', -// } -// }); - - -// console.log('...response...DELETE'); -// console.log(JSON.stringify(response, null, 2)); - - - -// // const webhookData = this.getWorkflowStaticData('node'); - -// // if (webhookData.webhookId !== undefined) { -// // const owner = this.getNodeParameter('owner') as string; -// // const repository = this.getNodeParameter('repository') as string; -// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; -// // const body = {}; - -// // try { -// // await githubApiRequest.call(this, 'DELETE', endpoint, body); -// // } catch (error) { -// // return false; -// // } - -// // // Remove from the static workflow data so that it is clear -// // // that no webhooks are registred anymore -// // delete webhookData.webhookId; -// // delete webhookData.webhookEvents; -// // } - -// return true; -// }, -// }, -// }; - - - -// async webhook(this: IWebhookFunctions): Promise { -// const bodyData = this.getBodyData(); - -// console.log(''); -// console.log(''); -// console.log('GOT WEBHOOK CALL'); -// console.log(JSON.stringify(bodyData, null, 2)); - - - -// // Check if the webhook is only the ping from Github to confirm if it workshook_id -// if (bodyData.hook_id !== undefined && bodyData.action === undefined) { -// // Is only the ping and not an actual webhook call. So return 'OK' -// // but do not start the workflow. - -// return { -// webhookResponse: 'OK' -// }; -// } - -// // Is a regular webhoook call - -// // TODO: Add headers & requestPath -// const returnData: IDataObject[] = []; - -// returnData.push( -// { -// body: bodyData, -// headers: this.getHeaderData(), -// query: this.getQueryData(), -// } -// ); - -// return { -// workflowData: [ -// this.helpers.returnJsonArray(returnData) -// ], -// }; -// } -// } +import { + IPollFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, + NodeApiError, +} from 'n8n-workflow'; + +import { + extractId, + googleApiRequest, + googleApiRequestAllItems, +} from './GenericFunctions'; + +import * as moment from 'moment'; + +export class GoogleDriveTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Google Drive Trigger', + name: 'googleDriveTrigger', + icon: 'file:googleDrive.svg', + group: ['trigger'], + version: 1, + description: 'Starts the workflow when Google Drive events occur', + subtitle: '={{$parameter["event"]}}', + defaults: { + name: 'Google Drive Trigger', + color: '#4285F4', + }, + credentials: [ + { + name: 'googleApi', + required: true, + displayOptions: { + show: { + authentication: [ + 'serviceAccount', + ], + }, + }, + }, + { + name: 'googleDriveOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, + ], + polling: true, + inputs: [], + outputs: ['main'], + properties: [ + { + displayName: 'Credential Type', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Service Account', + value: 'serviceAccount', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'oAuth2', + }, + { + displayName: 'Trigger On', + name: 'triggerOn', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'Changes to a Specific File', + value: 'specificFile', + }, + { + name: 'Changes Involving a Specific Folder', + value: 'specificFolder', + }, + // { + // name: 'Changes To Any File/Folder', + // value: 'anyFileFolder', + // }, + ], + description: '', + }, + { + displayName: 'File URL or ID', + name: 'fileToWatch', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'specificFile', + ], + }, + }, + default: '', + description: 'The address of this file when you view it in your browser (or just the ID contained within the URL)', + required: true, + }, + { + displayName: 'Watch For', + name: 'event', + type: 'options', + displayOptions: { + show: { + triggerOn: [ + 'specificFile', + ], + }, + }, + required: true, + default: 'fileUpdated', + options: [ + { + name: 'File Updated', + value: 'fileUpdated', + }, + ], + description: 'When to trigger this node', + }, + { + displayName: 'Folder URL or ID', + name: 'folderToWatch', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'specificFolder', + ], + }, + }, + default: '', + description: 'The address of this folder when you view it in your browser (or just the ID contained within the URL)', + required: true, + }, + { + displayName: 'Watch For', + name: 'event', + type: 'options', + displayOptions: { + show: { + triggerOn: [ + 'specificFolder', + ], + }, + }, + required: true, + default: '', + options: [ + { + name: 'File Created', + value: 'fileCreated', + description: 'When a file is created in the watched folder', + }, + { + name: 'File Updated', + value: 'fileUpdated', + description: 'When a file is updated in the watched folder', + }, + { + name: 'Folder Created', + value: 'folderCreated', + description: 'When a folder is created in the watched folder', + }, + { + name: 'Folder Updated', + value: 'folderUpdated', + description: 'When a folder is updated in the watched folder', + }, + { + name: 'Watch Folder Updated', + value: 'watchFolderUpdated', + description: 'When the watched folder itself is modified', + }, + ], + }, + { + displayName: 'Changes within subfolders won\'t trigger this node', + name: 'asas', + type: 'notice', + displayOptions: { + show: { + triggerOn: [ + 'specificFolder', + ], + }, + hide: { + event: [ + 'watchFolderUpdated', + ], + }, + }, + default: '', + }, + { + displayName: 'Drive To Watch', + name: 'driveToWatch', + type: 'options', + displayOptions: { + show: { + triggerOn: [ + 'anyFileFolder', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getDrives', + }, + default: 'root', + required: true, + description: 'The drive to monitor', + }, + { + displayName: 'Watch For', + name: 'event', + type: 'options', + displayOptions: { + show: { + triggerOn: [ + 'anyFileFolder', + ], + }, + }, + required: true, + default: 'fileCreated', + options: [ + { + name: 'File Created', + value: 'fileCreated', + description: 'When a file is created in the watched drive', + }, + { + name: 'File Updated', + value: 'fileUpdated', + description: 'When a file is updated in the watched drive', + }, + { + name: 'Folder Created', + value: 'folderCreated', + description: 'When a folder is created in the watched drive', + }, + { + name: 'Folder Updated', + value: 'folderUpdated', + description: 'When a folder is updated in the watched drive', + }, + ], + description: 'When to trigger this node', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + event: [ + 'fileCreated', + 'fileUpdated', + ], + }, + hide: { + triggerOn: [ + 'specificFile', + ], + }, + }, + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'File Type', + name: 'fileType', + type: 'options', + options: [ + { + name: '[All]', + value: 'all', + }, + { + name: 'Audio', + value: 'application/vnd.google-apps.audio', + }, + { + name: 'Google Docs', + value: 'application/vnd.google-apps.document', + }, + { + name: 'Google Drawings', + value: 'application/vnd.google-apps.drawing', + }, + { + name: 'Google Slides', + value: 'application/vnd.google-apps.presentation', + }, + { + name: 'Google Spreadsheets', + value: 'application/vnd.google-apps.spreadsheet', + }, + { + name: 'Photos and Images', + value: 'application/vnd.google-apps.photo', + }, + { + name: 'Videos', + value: 'application/vnd.google-apps.video', + }, + ], + default: 'all', + description: 'Triggers only when the file is this type', + }, + ], + }, + ], + }; + + methods = { + loadOptions: { + // Get all the calendars to display them to user so that he can + // select them easily + async getDrives( + this: ILoadOptionsFunctions, + ): Promise { + const returnData: INodePropertyOptions[] = []; + const drives = await googleApiRequestAllItems.call(this, 'drives', 'GET', `/drive/v3/drives`); + returnData.push({ + name: 'Root', + value: 'root', + }); + for (const drive of drives) { + returnData.push({ + name: drive.name, + value: drive.id, + }); + } + return returnData; + }, + }, + }; + + async poll(this: IPollFunctions): Promise { + const triggerOn = this.getNodeParameter('triggerOn') as string; + const event = this.getNodeParameter('event') as string; + const webhookData = this.getWorkflowStaticData('node'); + const options = this.getNodeParameter('options', {}) as IDataObject; + const qs: IDataObject = {}; + + const now = moment().utc().format(); + + const startDate = webhookData.lastTimeChecked as string || now; + + const endDate = now; + + const query = [ + 'trashed = false', + ]; + + if (triggerOn === 'specificFolder' && event !== 'watchFolderUpdated') { + const folderToWatch = extractId(this.getNodeParameter('folderToWatch') as string); + query.push(`'${folderToWatch}' in parents`); + } + + // if (triggerOn === 'anyFileFolder') { + // const driveToWatch = this.getNodeParameter('driveToWatch'); + // query.push(`'${driveToWatch}' in parents`); + // } + + if (event.startsWith('file')) { + query.push(`mimeType != 'application/vnd.google-apps.folder'`); + } else { + query.push(`mimeType = 'application/vnd.google-apps.folder'`); + } + + if (options.fileType && options.fileType !== 'all') { + query.push(`mimeType = '${options.fileType}'`); + } + + if (this.getMode() !== 'manual') { + if (event.includes('Created')) { + query.push(`createdTime > '${startDate}'`); + } else { + query.push(`modifiedTime > '${startDate}'`); + } + } + + qs.q = query.join(' AND '); + + qs.fields = 'nextPageToken, files(*)'; + + let files; + + if (this.getMode() === 'manual') { + qs.pageSize = 1; + files = await googleApiRequest.call(this, 'GET', `/drive/v3/files`, {}, qs); + files = files.files; + } else { + files = await googleApiRequestAllItems.call(this, 'files', 'GET', `/drive/v3/files`, {}, qs); + } + + if (triggerOn === 'specificFile' && this.getMode() !== 'manual') { + const fileToWatch = extractId(this.getNodeParameter('fileToWatch') as string); + files = files.filter((file: { id: string }) => file.id === fileToWatch); + } + + if (triggerOn === 'specificFolder' && event === 'watchFolderUpdated' && this.getMode() !== 'manual') { + const folderToWatch = extractId(this.getNodeParameter('folderToWatch') as string); + files = files.filter((file: { id: string }) => file.id === folderToWatch); + } + + webhookData.lastTimeChecked = endDate; + + if (Array.isArray(files) && files.length) { + return [this.helpers.returnJsonArray(files)]; + } + + if (this.getMode() === 'manual') { + throw new NodeApiError(this.getNode(), { message: 'No data with the current filter could be found' }); + } + + return null; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index b5c91861f4..6233219a5f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -419,6 +419,7 @@ "dist/nodes/Google/Contacts/GoogleContacts.node.js", "dist/nodes/Google/Docs/GoogleDocs.node.js", "dist/nodes/Google/Drive/GoogleDrive.node.js", + "dist/nodes/Google/Drive/GoogleDriveTrigger.node.js", "dist/nodes/Google/Firebase/CloudFirestore/CloudFirestore.node.js", "dist/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.js", "dist/nodes/Google/Gmail/Gmail.node.js", From d25353b6e66b1e2ef6a4347afd58f307e4c79b6f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 21 Oct 2021 13:34:16 -0500 Subject: [PATCH 020/452] :zap: Change log message from info to debug level --- packages/core/src/ActiveWorkflows.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index d68d3ea2da..4cdcd34594 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -194,7 +194,7 @@ export class ActiveWorkflows { // The trigger function to execute when the cron-time got reached const executeTrigger = async () => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - Logger.info(`Polling trigger initiated for workflow "${workflow.name}"`, { + Logger.debug(`Polling trigger initiated for workflow "${workflow.name}"`, { workflowName: workflow.name, workflowId: workflow.id, }); From cba2874c2e9fe9170f9f65f436384de7db7df6b8 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 21 Oct 2021 15:12:15 -0500 Subject: [PATCH 021/452] :bug: Fix useQuerystring in combination with x-www-form-urlencoded #2348 --- packages/core/src/NodeExecuteFunctions.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 2854f618ec..9aa1cf3e8d 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -162,16 +162,19 @@ async function parseRequestObject(requestObject: IDataObject) { // and also using formData. Request lib takes precedence for the formData. // We will do the same. // Merge body and form properties. - // @ts-ignore - axiosConfig.data = - typeof requestObject.body === 'string' - ? requestObject.body - : new URLSearchParams( - Object.assign(requestObject.body || {}, requestObject.form || {}) as Record< - string, - string - >, - ); + if (typeof requestObject.body === 'string') { + axiosConfig.data = requestObject.body; + } else { + const allData = Object.assign(requestObject.body || {}, requestObject.form || {}) as Record< + string, + string + >; + if (requestObject.useQuerystring === true) { + axiosConfig.data = stringify(allData, { arrayFormat: 'repeat' }); + } else { + axiosConfig.data = stringify(allData); + } + } } else if (contentType && contentType.includes('multipart/form-data') !== false) { if (requestObject.formData !== undefined && requestObject.formData instanceof FormData) { axiosConfig.data = requestObject.formData; From bb05f8113d3a313864e8009bee286bfb8b0a6ae6 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 21 Oct 2021 17:32:40 -0400 Subject: [PATCH 022/452] :sparkles: Add AWS Textract node (#2330) * :sparkles: AWS Textract node * :zap: Fix issue with types * :zap: Small change to labels * :zap: Improvements * :zap: Improve error message * :zap: Improve description Co-authored-by: Jan Oberhauser --- .../nodes-base/credentials/Aws.credentials.ts | 2 +- .../nodes-base/credentials/S3.credentials.ts | 2 +- .../nodes/Aws/Textract/AwsTextract.node.ts | 163 ++++++++++++++++++ .../nodes/Aws/Textract/GenericFunctions.ts | 156 +++++++++++++++++ .../nodes/Aws/Textract/textract.svg | 18 ++ packages/nodes-base/package.json | 1 + 6 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.ts create mode 100644 packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Aws/Textract/textract.svg diff --git a/packages/nodes-base/credentials/Aws.credentials.ts b/packages/nodes-base/credentials/Aws.credentials.ts index 5138b0c9a6..efb5fe59aa 100644 --- a/packages/nodes-base/credentials/Aws.credentials.ts +++ b/packages/nodes-base/credentials/Aws.credentials.ts @@ -17,7 +17,7 @@ export class Aws implements ICredentialType { default: 'us-east-1', }, { - displayName: 'Access Key Id', + displayName: 'Access Key ID', name: 'accessKeyId', type: 'string', default: '', diff --git a/packages/nodes-base/credentials/S3.credentials.ts b/packages/nodes-base/credentials/S3.credentials.ts index 861ef19aea..976f5910ed 100644 --- a/packages/nodes-base/credentials/S3.credentials.ts +++ b/packages/nodes-base/credentials/S3.credentials.ts @@ -22,7 +22,7 @@ export class S3 implements ICredentialType { default: 'us-east-1', }, { - displayName: 'Access Key Id', + displayName: 'Access Key ID', name: 'accessKeyId', type: 'string', default: '', diff --git a/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.ts b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.ts new file mode 100644 index 0000000000..64e7c1476b --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.ts @@ -0,0 +1,163 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IBinaryKeyData, + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeCredentialTestResult, + NodeOperationError, +} from 'n8n-workflow'; + +import { + awsApiRequestREST, + IExpenseDocument, + simplify, + validateCrendetials, +} from './GenericFunctions'; + +export class AwsTextract implements INodeType { + description: INodeTypeDescription = { + displayName: 'AWS Textract', + name: 'awsTextract', + icon: 'file:textract.svg', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"]}}', + description: 'Sends data to Amazon Textract', + defaults: { + name: 'AWS Textract', + color: '#5aa08d', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'aws', + required: true, + testedBy: 'awsTextractApiCredentialTest', + }, + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Analyze Receipt or Invoice', + value: 'analyzeExpense', + }, + ], + default: 'analyzeExpense', + description: '', + }, + { + displayName: 'Input Data Field Name', + name: 'binaryPropertyName', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'analyzeExpense', + ], + }, + }, + required: true, + description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG', + }, + { + displayName: 'Simplify Response', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'analyzeExpense', + ], + }, + }, + default: true, + description: 'Return a simplified version of the response instead of the raw data.', + }, + ], + }; + + methods = { + credentialTest: { + async awsTextractApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + try { + await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject, 'sts'); + } catch (error) { + return { + status: 'Error', + message: 'The security token included in the request is invalid', + }; + } + + return { + status: 'OK', + message: 'Connection successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let responseData; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < items.length; i++) { + try { + //https://docs.aws.amazon.com/textract/latest/dg/API_AnalyzeExpense.html + if (operation === 'analyzeExpense') { + const binaryProperty = this.getNodeParameter('binaryPropertyName', i) as string; + const simple = this.getNodeParameter('simple', i) as boolean; + + if (items[i].binary === undefined) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!'); + } + + if ((items[i].binary as IBinaryKeyData)[binaryProperty] === undefined) { + throw new NodeOperationError(this.getNode(), `No binary data property "${binaryProperty}" does not exists on item!`); + } + + const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryProperty]; + + const body: IDataObject = { + Document: { + Bytes: binaryPropertyData.data, + }, + }; + + const action = 'Textract.AnalyzeExpense'; + responseData = await awsApiRequestREST.call(this, 'textract', 'POST', '', JSON.stringify(body), { 'x-amz-target': action, 'Content-Type': 'application/x-amz-json-1.1' }) as IExpenseDocument; + if (simple) { + responseData = simplify(responseData); + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as unknown as IDataObject); + } + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts new file mode 100644 index 0000000000..9758552cf4 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts @@ -0,0 +1,156 @@ +import { + URL, +} from 'url'; + +import { + Request, + sign, +} from 'aws4'; + +import { + OptionsWithUri, +} from 'request'; + +import { + parseString, +} from 'xml2js'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialTestFunctions, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string { + let endpoint; + if (service === 'lambda' && credentials.lambdaEndpoint) { + endpoint = credentials.lambdaEndpoint; + } else if (service === 'sns' && credentials.snsEndpoint) { + endpoint = credentials.snsEndpoint; + } else { + endpoint = `https://${service}.${credentials.region}.amazonaws.com`; + } + return (endpoint as string).replace('{region}', credentials.region as string); +} + +export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const credentials = await this.getCredentials('aws'); + if (credentials === undefined) { + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); + } + + // Concatenate path and instantiate URL object so it parses correctly query strings + const endpoint = new URL(getEndpointForService(service, credentials) + path); + + // Sign AWS API request with the user credentials + const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; + sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + + + const options: OptionsWithUri = { + headers: signOpts.headers, + method, + uri: endpoint.href, + body: signOpts.body, + }; + + try { + return await this.helpers.request!(options); + } catch (error) { + if (error?.response?.data || error?.response?.body) { + const errorMessage = error?.response?.data || error?.response?.body; + if (errorMessage.includes('AccessDeniedException')) { + const user = JSON.parse(errorMessage).Message.split(' ')[1]; + throw new NodeApiError(this.getNode(), error, { + message: 'Unauthorized — please check your AWS policy configuration', + description: `Make sure an identity-based policy allows user ${user} to perform textract:AnalyzeExpense` }); + } + } + + throw new NodeApiError(this.getNode(), error); // no XML parsing needed + } +} + +export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const response = await awsApiRequest.call(this, service, method, path, body, headers); + try { + return JSON.parse(response); + } catch (error) { + return response; + } +} + +export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const response = await awsApiRequest.call(this, service, method, path, body, headers); + try { + return await new Promise((resolve, reject) => { + parseString(response, { explicitArray: false }, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } catch (error) { + return response; + } +} + +export function simplify(data: IExpenseDocument) { + const result: { [key: string]: string } = {}; + for (const document of data.ExpenseDocuments) { + for (const field of document.SummaryFields) { + result[field?.Type?.Text || field?.LabelDetection?.Text] = field.ValueDetection.Text; + } + } + return result; +} + +export interface IExpenseDocument { + ExpenseDocuments: [ + { + SummaryFields: [ + { + LabelDetection: { Text: string }, + ValueDetection: { Text: string }, + Type: { Text: string } + }] + }]; +} + +export async function validateCrendetials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject, service: string): Promise { // tslint:disable-line:no-any + const credentials = decryptedCredentials; + + // Concatenate path and instantiate URL object so it parses correctly query strings + const endpoint = new URL(getEndpointForService(service, credentials) + `?Action=GetCallerIdentity&Version=2011-06-15`); + + // Sign AWS API request with the user credentials + const signOpts = { host: endpoint.host, method: 'POST', path: '?Action=GetCallerIdentity&Version=2011-06-15' } as Request; + sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + + const options: OptionsWithUri = { + headers: signOpts.headers, + method: 'POST', + uri: endpoint.href, + body: signOpts.body, + }; + + const response = await this.helpers.request!(options); + + return await new Promise((resolve, reject) => { + parseString(response, { explicitArray: false }, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); +} diff --git a/packages/nodes-base/nodes/Aws/Textract/textract.svg b/packages/nodes-base/nodes/Aws/Textract/textract.svg new file mode 100644 index 0000000000..fa00d88c01 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Textract/textract.svg @@ -0,0 +1,18 @@ + + + + Icon-Architecture/64/Arch_AWS-Textract_64 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6233219a5f..ea8db2359d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -330,6 +330,7 @@ "dist/nodes/Aws/S3/AwsS3.node.js", "dist/nodes/Aws/SES/AwsSes.node.js", "dist/nodes/Aws/SQS/AwsSqs.node.js", + "dist/nodes/Aws/Textract/AwsTextract.node.js", "dist/nodes/Aws/Transcribe/AwsTranscribe.node.js", "dist/nodes/Aws/AwsSns.node.js", "dist/nodes/Aws/AwsSnsTrigger.node.js", From 03f6e30babdee26141c2398f07ae8aaeaf12d4f0 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 21 Oct 2021 18:01:05 -0400 Subject: [PATCH 023/452] :zap: Remove deprecated resources - Bitbucket Trigger (#2350) * :zap: Remove deprecated resources Fixes #2228 * :zap: Fix parameter order Co-authored-by: Jan Oberhauser --- .../nodes/Bitbucket/BitbucketTrigger.node.ts | 180 ++++++------------ 1 file changed, 55 insertions(+), 125 deletions(-) diff --git a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts index 665a7b1053..b93501cc6c 100644 --- a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts +++ b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts @@ -58,58 +58,36 @@ export class BitbucketTrigger implements INodeType { type: 'options', required: true, options: [ - { - name: 'User', - value: 'user', - }, - { - name: 'Team', - value: 'team', - }, { name: 'Repository', value: 'repository', }, + { + name: 'Workspace', + value: 'workspace', + }, ], - default: 'user', + default: 'workspace', description: 'The resource to operate on.', }, { - displayName: 'Events', - name: 'events', - type: 'multiOptions', - displayOptions: { - show: { - resource: [ - 'user', - ], - }, - }, - typeOptions: { - loadOptionsMethod: 'getUsersEvents', - }, - options: [], - required: true, - default: [], - description: 'The events to listen to.', - }, - { - displayName: 'Team', - name: 'team', + displayName: 'Workspace', + name: 'workspace', type: 'options', displayOptions: { show: { resource: [ - 'team', + 'workspace', + 'repository', ], }, }, typeOptions: { - loadOptionsMethod: 'getTeams', + loadOptionsMethod: 'getWorkspaces', }, required: true, default: '', - description: 'The team of which to listen to the events.', + description: 'The repository of which to listen to the events.', }, { displayName: 'Events', @@ -118,12 +96,12 @@ export class BitbucketTrigger implements INodeType { displayOptions: { show: { resource: [ - 'team', + 'workspace', ], }, }, typeOptions: { - loadOptionsMethod: 'getTeamEvents', + loadOptionsMethod: 'getWorkspaceEvents', }, options: [], required: true, @@ -143,6 +121,9 @@ export class BitbucketTrigger implements INodeType { }, typeOptions: { loadOptionsMethod: 'getRepositories', + loadOptionsDependsOn: [ + 'workspace', + ], }, required: true, default: '', @@ -208,89 +189,50 @@ export class BitbucketTrigger implements INodeType { }, }, loadOptions: { - // Get all the events to display them to user so that he can - // select them easily - async getUsersEvents(this: ILoadOptionsFunctions): Promise { + async getWorkspaceEvents(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/user'); + const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/workspace'); for (const event of events) { - const eventName = event.event; - const eventId = event.event; - const eventDescription = event.description; returnData.push({ - name: eventName, - value: eventId, - description: eventDescription, + name: event.event, + value: event.event, + description: event.description, }); } return returnData; }, - // Get all the events to display them to user so that he can - // select them easily - async getTeamEvents(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/team'); - for (const event of events) { - const eventName = event.event; - const eventId = event.event; - const eventDescription = event.description; - returnData.push({ - name: eventName, - value: eventId, - description: eventDescription, - }); - } - return returnData; - }, - // Get all the events to display them to user so that he can - // select them easily async getRepositoriesEvents(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/repository'); for (const event of events) { - const eventName = event.event; - const eventId = event.event; - const eventDescription = event.description; returnData.push({ - name: eventName, - value: eventId, - description: eventDescription, + name: event.event, + value: event.event, + description: event.description, }); } return returnData; }, - // Get all the repositories to display them to user so that he can - // select them easily async getRepositories(this: ILoadOptionsFunctions): Promise { - const credentials = await this.getCredentials('bitbucketApi'); const returnData: INodePropertyOptions[] = []; - const repositories = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', `/repositories/${credentials!.username}`); + const workspace = this.getCurrentNodeParameter('workspace') as string; + const repositories = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', `/repositories/${workspace}`); for (const repository of repositories) { - const repositoryName = repository.slug; - const repositoryId = repository.slug; - const repositoryDescription = repository.description; returnData.push({ - name: repositoryName, - value: repositoryId, - description: repositoryDescription, + name: repository.slug, + value: repository.slug, + description: repository.description, }); } return returnData; }, - // Get all the teams to display them to user so that he can - // select them easily - async getTeams(this: ILoadOptionsFunctions): Promise { + async getWorkspaces(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const qs: IDataObject = { - role: 'member', - }; - const teams = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/teams', {}, qs); - for (const team of teams) { - const teamName = team.display_name; - const teamId = team.username; + const workspaces = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', `/workspaces`); + for (const workspace of workspaces) { returnData.push({ - name: teamName, - value: teamId, + name: workspace.name, + value: workspace.slug, }); } return returnData; @@ -302,29 +244,25 @@ export class BitbucketTrigger implements INodeType { default: { async checkExists(this: IHookFunctions): Promise { let endpoint = ''; - const credentials = await this.getCredentials('bitbucketApi'); const resource = this.getNodeParameter('resource', 0) as string; + const workspace = this.getNodeParameter('workspace', 0) as string; + const webhookUrl = this.getNodeWebhookUrl('default'); const webhookData = this.getWorkflowStaticData('node'); - if (webhookData.webhookId === undefined) { - return false; - } - if (resource === 'user') { - endpoint = `/users/${credentials!.username}/hooks/${webhookData.webhookId}`; - } - if (resource === 'team') { - const team = this.getNodeParameter('team', 0) as string; - endpoint = `/teams/${team}/hooks/${webhookData.webhookId}`; + if (resource === 'workspace') { + endpoint = `/workspaces/${workspace}/hooks`; } if (resource === 'repository') { const repository = this.getNodeParameter('repository', 0) as string; - endpoint = `/repositories/${credentials!.username}/${repository}/hooks/${webhookData.webhookId}`; + endpoint = `/repositories/${workspace}/${repository}/hooks`; } - try { - await bitbucketApiRequest.call(this, 'GET', endpoint); - } catch (error) { - return false; + const { values: hooks } = await bitbucketApiRequest.call(this, 'GET', endpoint); + for (const hook of hooks) { + if (webhookUrl === hook.url && hook.active === true) { + webhookData.webhookId = hook.uuid.replace('{', '').replace('}', ''); + return true; + } } - return true; + return false; }, async create(this: IHookFunctions): Promise { let responseData; @@ -333,18 +271,14 @@ export class BitbucketTrigger implements INodeType { const webhookData = this.getWorkflowStaticData('node'); const events = this.getNodeParameter('events') as string[]; const resource = this.getNodeParameter('resource', 0) as string; - const credentials = await this.getCredentials('bitbucketApi'); + const workspace = this.getNodeParameter('workspace', 0) as string; - if (resource === 'user') { - endpoint = `/users/${credentials!.username}/hooks`; - } - if (resource === 'team') { - const team = this.getNodeParameter('team', 0) as string; - endpoint = `/teams/${team}/hooks`; + if (resource === 'workspace') { + endpoint = `/workspaces/${workspace}/hooks`; } if (resource === 'repository') { const repository = this.getNodeParameter('repository', 0) as string; - endpoint = `/repositories/${credentials!.username}/${repository}/hooks`; + endpoint = `/repositories/${workspace}/${repository}/hooks`; } const body: IDataObject = { description: 'n8n webhook', @@ -359,22 +293,18 @@ export class BitbucketTrigger implements INodeType { async delete(this: IHookFunctions): Promise { let endpoint = ''; const webhookData = this.getWorkflowStaticData('node'); - const credentials = await this.getCredentials('bitbucketApi'); + const workspace = this.getNodeParameter('workspace', 0) as string; const resource = this.getNodeParameter('resource', 0) as string; - if (resource === 'user') { - endpoint = `/users/${credentials!.username}/hooks/${webhookData.webhookId}`; - } - if (resource === 'team') { - const team = this.getNodeParameter('team', 0) as string; - endpoint = `/teams/${team}/hooks/${webhookData.webhookId}`; + if (resource === 'workspace') { + endpoint = `/workspaces/${workspace}/hooks/${webhookData.webhookId}`; } if (resource === 'repository') { const repository = this.getNodeParameter('repository', 0) as string; - endpoint = `/repositories/${credentials!.username}/${repository}/hooks/${webhookData.webhookId}`; + endpoint = `/repositories/${workspace}/${repository}/hooks/${webhookData.webhookId}`; } try { await bitbucketApiRequest.call(this, 'DELETE', endpoint); - } catch(error) { + } catch (error) { return false; } delete webhookData.webhookId; From 0b56db56170c016f592cf06657a8d8eb24ce9873 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 21 Oct 2021 18:04:08 -0500 Subject: [PATCH 024/452] :zap: Remove not used code --- packages/cli/src/Server.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 3532ad9c4b..61a58febb5 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -54,7 +54,6 @@ import { import { ICredentialsDecrypted, - ICredentialsEncrypted, ICredentialType, IDataObject, INodeCredentials, @@ -67,12 +66,12 @@ import { INodeVersionedType, ITelemetrySettings, IWorkflowBase, - IWorkflowCredentials, LoggerProxy, NodeCredentialTestRequest, NodeCredentialTestResult, NodeHelpers, Workflow, + ICredentialsEncrypted, WorkflowExecuteMode, } from 'n8n-workflow'; @@ -1820,12 +1819,6 @@ class App { return ResponseHelper.sendErrorResponse(res, errorResponse); } - // Decrypt the currently saved credentials - const workflowCredentials: IWorkflowCredentials = { - [result.type]: { - [result.id.toString()]: result as ICredentialsEncrypted, - }, - }; const mode: WorkflowExecuteMode = 'internal'; const credentialsHelper = new CredentialsHelper(encryptionKey); const decryptedDataOriginal = await credentialsHelper.getDecrypted( @@ -2044,13 +2037,6 @@ class App { return ResponseHelper.sendErrorResponse(res, errorResponse); } - // Decrypt the currently saved credentials - const workflowCredentials: IWorkflowCredentials = { - [result.type]: { - [result.id.toString()]: result as ICredentialsEncrypted, - }, - }; - const mode: WorkflowExecuteMode = 'internal'; const credentialsHelper = new CredentialsHelper(encryptionKey); const decryptedDataOriginal = await credentialsHelper.getDecrypted( From a0b89ee5a4e6c375ac87ea8b62f3a92de66a6413 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 21 Oct 2021 18:24:43 -0500 Subject: [PATCH 025/452] :zap: Remove more not used code --- packages/cli/commands/execute.ts | 2 -- packages/cli/commands/executeBatch.ts | 6 +----- packages/cli/commands/start.ts | 5 +---- packages/cli/commands/update/workflow.ts | 3 +-- packages/cli/commands/webhook.ts | 2 -- packages/cli/commands/worker.ts | 14 ++------------ 6 files changed, 5 insertions(+), 27 deletions(-) diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index 374c753e99..b6641628df 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -15,8 +15,6 @@ import { IWorkflowExecutionDataProcess, LoadNodesAndCredentials, NodeTypes, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowHelpers, WorkflowRunner, } from '../src'; diff --git a/packages/cli/commands/executeBatch.ts b/packages/cli/commands/executeBatch.ts index ec4d68dd03..d4489c38d4 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -12,7 +12,7 @@ import { Command, flags } from '@oclif/command'; import { UserSettings } from 'n8n-core'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { INode, INodeExecutionData, ITaskData, LoggerProxy } from 'n8n-workflow'; +import { INode, ITaskData, LoggerProxy } from 'n8n-workflow'; import { sep } from 'path'; @@ -28,14 +28,10 @@ import { CredentialTypes, Db, ExternalHooks, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - IExecutionsCurrentSummary, IWorkflowDb, IWorkflowExecutionDataProcess, LoadNodesAndCredentials, NodeTypes, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowRunner, } from '../src'; diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 3ecbddc0e7..d12957209a 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -22,8 +22,6 @@ import { Db, ExternalHooks, GenericHelpers, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - IExecutionsCurrentSummary, InternalHooksManager, LoadNodesAndCredentials, NodeTypes, @@ -320,8 +318,7 @@ export class Start extends Command { activeWorkflowRunner = ActiveWorkflowRunner.getInstance(); await activeWorkflowRunner.init(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const waitTracker = WaitTracker(); + WaitTracker(); const editorUrl = GenericHelpers.getBaseUrl(); this.log(`\nEditor is now accessible via:\n${editorUrl}`); diff --git a/packages/cli/commands/update/workflow.ts b/packages/cli/commands/update/workflow.ts index b3d777de7d..7c598b4e82 100644 --- a/packages/cli/commands/update/workflow.ts +++ b/packages/cli/commands/update/workflow.ts @@ -4,8 +4,7 @@ import { Command, flags } from '@oclif/command'; import { IDataObject, LoggerProxy } from 'n8n-workflow'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Db, GenericHelpers } from '../../src'; +import { Db } from '../../src'; import { getLogger } from '../../src/Logger'; diff --git a/packages/cli/commands/webhook.ts b/packages/cli/commands/webhook.ts index 1600877cbe..97876e5235 100644 --- a/packages/cli/commands/webhook.ts +++ b/packages/cli/commands/webhook.ts @@ -20,8 +20,6 @@ import { GenericHelpers, LoadNodesAndCredentials, NodeTypes, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - TestWebhooks, WebhookServer, } from '../src'; diff --git a/packages/cli/commands/worker.ts b/packages/cli/commands/worker.ts index 32a7d24ccb..baacad83b9 100644 --- a/packages/cli/commands/worker.ts +++ b/packages/cli/commands/worker.ts @@ -12,21 +12,12 @@ import * as PCancelable from 'p-cancelable'; import { Command, flags } from '@oclif/command'; import { UserSettings, WorkflowExecute } from 'n8n-core'; -import { - IDataObject, - INodeTypes, - IRun, - IWorkflowExecuteHooks, - Workflow, - WorkflowHooks, - LoggerProxy, -} from 'n8n-workflow'; +import { INodeTypes, IRun, Workflow, LoggerProxy } from 'n8n-workflow'; import { FindOneOptions } from 'typeorm'; import * as Bull from 'bull'; import { - ActiveExecutions, CredentialsOverwrites, CredentialTypes, Db, @@ -39,7 +30,6 @@ import { LoadNodesAndCredentials, NodeTypes, ResponseHelper, - WorkflowCredentials, WorkflowExecuteAdditionalData, } from '../src'; @@ -203,7 +193,7 @@ export class Worker extends Command { Worker.runningJobs[job.id] = workflowRun; // Wait till the execution is finished - const runData = await workflowRun; + await workflowRun; delete Worker.runningJobs[job.id]; From 6fa8dd701e966f1276403240f0f430272b8157b7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 21 Oct 2021 18:25:31 -0500 Subject: [PATCH 026/452] :zap: Fix issue with queue mode --- packages/cli/commands/start.ts | 3 +++ packages/cli/commands/webhook.ts | 4 ++++ packages/cli/commands/worker.ts | 5 ++++- packages/cli/src/Server.ts | 2 -- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index d12957209a..253877e564 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -312,6 +312,9 @@ export class Start extends Command { ); } + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + await Server.start(); // Start to get active workflows and run their triggers diff --git a/packages/cli/commands/webhook.ts b/packages/cli/commands/webhook.ts index 97876e5235..a5f926682f 100644 --- a/packages/cli/commands/webhook.ts +++ b/packages/cli/commands/webhook.ts @@ -18,6 +18,7 @@ import { Db, ExternalHooks, GenericHelpers, + InternalHooksManager, LoadNodesAndCredentials, NodeTypes, WebhookServer, @@ -147,6 +148,9 @@ export class Webhook extends Command { // Wait till the database is ready await startDbInitPromise; + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + if (config.get('executions.mode') === 'queue') { const redisHost = config.get('queue.bull.redis.host'); const redisPassword = config.get('queue.bull.redis.password'); diff --git a/packages/cli/commands/worker.ts b/packages/cli/commands/worker.ts index baacad83b9..9a06868f3a 100644 --- a/packages/cli/commands/worker.ts +++ b/packages/cli/commands/worker.ts @@ -26,7 +26,7 @@ import { IBullJobData, IBullJobResponse, IExecutionFlattedDb, - IExecutionResponse, + InternalHooksManager, LoadNodesAndCredentials, NodeTypes, ResponseHelper, @@ -259,6 +259,9 @@ export class Worker extends Command { // eslint-disable-next-line @typescript-eslint/no-floating-promises Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, nodeTypes)); + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + const versions = await GenericHelpers.getVersions(); console.info('\nn8n worker is now ready'); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 61a58febb5..e9de494f44 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -312,8 +312,6 @@ class App { this.frontendSettings.personalizationSurvey = await PersonalizationSurvey.preparePersonalizationSurvey(); - InternalHooksManager.init(this.frontendSettings.instanceId); - await this.externalHooks.run('frontend.settings', [this.frontendSettings]); const excludeEndpoints = config.get('security.excludeEndpoints') as string; From b5f12bab00e10942ee240962e17b8a4b9f034a43 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 21 Oct 2021 20:14:52 -0400 Subject: [PATCH 027/452] :bug: Fix issue with _where parameter with request library (#2352) Fixes #2324 --- packages/nodes-base/nodes/Strapi/GenericFunctions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts index e8112b05ce..e1338376a4 100644 --- a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts @@ -25,6 +25,9 @@ export async function strapiApiRequest(this: IExecuteFunctions | ILoadOptionsFun qs, uri: uri || `${credentials.url}${resource}`, json: true, + qsStringifyOptions: { + arrayFormat: 'indice', + }, }; if (Object.keys(headers).length !== 0) { options.headers = Object.assign({}, options.headers, headers); From a344483e62428527363892fe75a0cf00816446ba Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:01 +0000 Subject: [PATCH 028/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@?= =?UTF-8?q?0.73.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/workflow/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 5fc838cb46..25c3c84429 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.72.0", + "version": "0.73.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 04fa803c1fdd65d32900d2b05cba03ae61326328 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:09 +0000 Subject: [PATCH 029/452] :arrow_up: Set n8n-workflow@0.73.0 on n8n-core --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 292b95ab73..1a322f5412 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,7 +50,7 @@ "form-data": "^4.0.0", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.72.0", + "n8n-workflow": "~0.73.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From 16d55a511cbd5d766e693036b7943c39fbeae40e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:09 +0000 Subject: [PATCH 030/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.90?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 1a322f5412..748e7a76a2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.89.0", + "version": "0.90.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From ca8d598f7475494823282e520b1cd1ae6789376b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:21 +0000 Subject: [PATCH 031/452] :arrow_up: Set n8n-core@0.90.0 and n8n-workflow@0.73.0 on n8n-node-dev --- packages/node-dev/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index b8fbc196aa..6729d45db0 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -60,8 +60,8 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", - "n8n-core": "~0.89.0", - "n8n-workflow": "~0.72.0", + "n8n-core": "~0.90.0", + "n8n-workflow": "~0.73.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From 5be57984cea24bf0df08f4654f147259572f2bda Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:21 +0000 Subject: [PATCH 032/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@?= =?UTF-8?q?0.30.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/node-dev/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 6729d45db0..fb98edd244 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.29.0", + "version": "0.30.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 108983fecbe4b75ac41e443400bdfa31ee924179 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:30 +0000 Subject: [PATCH 033/452] :arrow_up: Set n8n-core@0.90.0 and n8n-workflow@0.73.0 on n8n-nodes-base --- packages/nodes-base/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ea8db2359d..523ee949cc 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -665,7 +665,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.72.0", + "n8n-workflow": "~0.73.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -705,7 +705,7 @@ "mssql": "^6.2.0", "mysql2": "~2.3.0", "node-ssh": "^12.0.0", - "n8n-core": "~0.89.0", + "n8n-core": "~0.90.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", "pg": "^8.3.0", From aa98cb943654404ca154aa86b7c68fe55d3e5369 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:28:30 +0000 Subject: [PATCH 034/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-bas?= =?UTF-8?q?e@0.142.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 523ee949cc..782f654359 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.141.0", + "version": "0.142.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From a974cb96ce38f6831deb684e147dcdec87be60d9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:29:05 +0000 Subject: [PATCH 035/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-design-sy?= =?UTF-8?q?stem@0.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/design-system/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 5825418f72..13fff02107 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "0.4.0", + "version": "0.5.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", "author": { From b27f6bc68b059fdd0d3039f8d0acc3028d6cc3b0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:29:12 +0000 Subject: [PATCH 036/452] :arrow_up: Set n8n-design-system@0.5.0 and n8n-workflow@0.73.0 on n8n-editor-ui --- packages/editor-ui/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index c7b980e71e..a463ab04ab 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@fontsource/open-sans": "^4.5.0", - "n8n-design-system": "~0.4.0", + "n8n-design-system": "~0.5.0", "timeago.js": "^4.0.2", "v-click-outside": "^3.1.2", "vue-fragment": "^1.5.2" @@ -71,7 +71,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.72.0", + "n8n-workflow": "~0.73.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From 5ca3a30a9744ee37773ec76db6a49cc2cca6ac51 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:29:12 +0000 Subject: [PATCH 037/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui?= =?UTF-8?q?@0.113.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index a463ab04ab..e6da2907b8 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.112.0", + "version": "0.113.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d54396281ccc9682468ab553409c1d684f5dbfd6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:29:45 +0000 Subject: [PATCH 038/452] :arrow_up: Set n8n-core@0.90.0, n8n-editor-ui@0.113.0, n8n-nodes-base@0.142.0 and n8n-workflow@0.73.0 on n8n --- packages/cli/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e9f52041e3..7306a2554a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -110,10 +110,10 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mysql2": "~2.3.0", - "n8n-core": "~0.89.0", - "n8n-editor-ui": "~0.112.0", - "n8n-nodes-base": "~0.141.0", - "n8n-workflow": "~0.72.0", + "n8n-core": "~0.90.0", + "n8n-editor-ui": "~0.113.0", + "n8n-nodes-base": "~0.142.0", + "n8n-workflow": "~0.73.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From 73931f73ce92fba041bb25fafd590fc03e3f8bb3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 00:29:45 +0000 Subject: [PATCH 039/452] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.145.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 7306a2554a..f9e398216f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.144.0", + "version": "0.145.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 37930057d8d8731a2e4fe8106338df396f610894 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Oct 2021 14:39:36 -0500 Subject: [PATCH 040/452] :bug: Fix build issue by setting fixed version of @types/node #2358 --- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- packages/editor-ui/package.json | 2 +- packages/node-dev/package.json | 2 +- packages/nodes-base/package.json | 2 +- packages/workflow/package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f9e398216f..2ab8191e77 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -66,7 +66,7 @@ "@types/jest": "^26.0.13", "@types/localtunnel": "^1.9.0", "@types/lodash.get": "^4.4.6", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "@types/open": "^6.1.0", "@types/parseurl": "^1.3.1", "@types/request-promise-native": "~1.0.15", diff --git a/packages/core/package.json b/packages/core/package.json index 748e7a76a2..88bfc1170b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,7 @@ "@types/jest": "^26.0.13", "@types/lodash.get": "^4.4.6", "@types/mime-types": "^2.1.0", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "@types/request-promise-native": "~1.0.15", "jest": "^26.4.2", "source-map-support": "^0.5.9", diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index e6da2907b8..2f381b5740 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -41,7 +41,7 @@ "@types/jest": "^26.0.13", "@types/lodash.get": "^4.4.6", "@types/lodash.set": "^4.3.6", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "@types/quill": "^2.0.1", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.29.0", diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index fb98edd244..93145b731a 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -56,7 +56,7 @@ "@oclif/command": "^1.5.18", "@oclif/errors": "^1.2.2", "@types/express": "^4.17.6", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 782f654359..c2135d5edf 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -655,7 +655,7 @@ "@types/mongodb": "^3.5.4", "@types/mqtt": "^2.5.0", "@types/mssql": "^6.0.2", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "@types/nodemailer": "^6.4.0", "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 25c3c84429..61d915a59b 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -30,7 +30,7 @@ "@types/express": "^4.17.6", "@types/jest": "^26.0.13", "@types/lodash.get": "^4.4.6", - "@types/node": "^14.14.40", + "@types/node": "14.17.27", "@types/xml2js": "^0.4.3", "@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/parser": "^4.29.0", From c63f365a658031fcb51202f062fe3e66885f9058 Mon Sep 17 00:00:00 2001 From: Arpad Gabor Date: Sat, 23 Oct 2021 18:38:46 +0300 Subject: [PATCH 041/452] feat: replace function node code editor with monaco --- packages/editor-ui/package.json | 4 +- .../editor-ui/src/components/CodeEdit.vue | 128 ++++++++++++------ .../src/components/ParameterInput.vue | 4 +- packages/editor-ui/vue.config.js | 5 + 4 files changed, 97 insertions(+), 44 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 2f381b5740..69eb7c1521 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -27,6 +27,7 @@ "dependencies": { "@fontsource/open-sans": "^4.5.0", "n8n-design-system": "~0.5.0", + "monaco-editor": "^0.29.1", "timeago.js": "^4.0.2", "v-click-outside": "^3.1.2", "vue-fragment": "^1.5.2" @@ -72,11 +73,12 @@ "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "n8n-workflow": "~0.73.0", - "sass": "^1.26.5", + "monaco-editor-webpack-plugin": "^5.0.0", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", "quill": "^2.0.0-dev.3", "quill-autoformat": "^0.1.1", + "sass": "^1.26.5", "sass-loader": "^8.0.2", "string-template-parser": "^1.2.6", "ts-jest": "^26.3.0", diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index fb1c02521d..85c7cc1c25 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -1,61 +1,107 @@ diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 5ea58f5f4c..30008448ad 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -13,7 +13,7 @@ />
- +
@@ -597,7 +597,7 @@ export default mixins( parameter_field_type: this.parameter.type, new_expression: !this.isValueExpression, workflow_id: this.$store.getters.workflowId, - }); + }); } }, closeTextEditDialog () { diff --git a/packages/editor-ui/vue.config.js b/packages/editor-ui/vue.config.js index d1233395fe..ef36eb00bc 100644 --- a/packages/editor-ui/vue.config.js +++ b/packages/editor-ui/vue.config.js @@ -1,3 +1,5 @@ +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); + module.exports = { chainWebpack: config => config.resolve.symlinks(false), // transpileDependencies: [ @@ -13,6 +15,9 @@ module.exports = { devServer: { disableHostCheck: true, }, + plugins: [ + new MonacoWebpackPlugin(), + ], }, css: { loaderOptions: { From 7a020d079fa564f895564c14339df2617ce470f5 Mon Sep 17 00:00:00 2001 From: Arpad Gabor Date: Sat, 23 Oct 2021 18:39:34 +0300 Subject: [PATCH 042/452] fix: set autocomplete data as empty array --- packages/editor-ui/src/components/CodeEdit.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index 85c7cc1c25..b3cfac943e 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -71,7 +71,7 @@ export default mixins(genericHelpers).extend({ loadAutocompleteData(): void { const executedWorkflow: IExecutionResponse | null = this.$store.getters.getWorkflowExecution; - let autocompleteData: INodeExecutionData[] = [{ json: { myNewField: 2 } }]; + let autocompleteData: INodeExecutionData[] = []; if (executedWorkflow) { const lastNodeExecuted = executedWorkflow.data.resultData.lastNodeExecuted; From 62f0899e100b11ba45509df796c1c5d342b1c78d Mon Sep 17 00:00:00 2001 From: Arpad Gabor Date: Sat, 23 Oct 2021 18:48:45 +0300 Subject: [PATCH 043/452] fix: forgot readonly to editor --- packages/editor-ui/src/components/CodeEdit.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index b3cfac943e..3a32ed7862 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -54,6 +54,7 @@ export default mixins(genericHelpers).extend({ value: this.value, language: 'javascript', tabSize: 2, + readOnly: this.isReadOnly, }); this.monacoInstance.onDidChangeModelContent((ev) => { From f811992527e15aa310fcef1603a69776adff6a7c Mon Sep 17 00:00:00 2001 From: Arpad Gabor Date: Sat, 23 Oct 2021 19:06:02 +0300 Subject: [PATCH 044/452] fix: monaco build problems --- packages/editor-ui/src/components/CodeEdit.vue | 11 +++++------ packages/editor-ui/vue.config.js | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index 3a32ed7862..b5bfd9a94e 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -57,12 +57,11 @@ export default mixins(genericHelpers).extend({ readOnly: this.isReadOnly, }); - this.monacoInstance.onDidChangeModelContent((ev) => { - if (this.monacoInstance) { - const model = this.monacoInstance.getModel(); - if (model) { - this.$emit('valueChanged', model.getValue()); - } + this.monacoInstance.onDidChangeModelContent(() => { + const model = this.monacoInstance!.getModel(); + + if (model) { + this.$emit('valueChanged', model.getValue()); } }); diff --git a/packages/editor-ui/vue.config.js b/packages/editor-ui/vue.config.js index ef36eb00bc..6f2e10804c 100644 --- a/packages/editor-ui/vue.config.js +++ b/packages/editor-ui/vue.config.js @@ -16,7 +16,7 @@ module.exports = { disableHostCheck: true, }, plugins: [ - new MonacoWebpackPlugin(), + new MonacoWebpackPlugin({ languages: ['javascript', 'html', 'typescript'] }), ], }, css: { @@ -28,5 +28,5 @@ module.exports = { }, }, }, - publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : '/', + publicPath: process.env.VUE_APP_PUBLIC_PATH && process.env.VUE_APP_PUBLIC_PATH !== '/%BASE_PATH%/' ? process.env.VUE_APP_PUBLIC_PATH : '/', }; From 119989bc3760b9a8b1a1e57bfa25c23e657e647a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 26 Oct 2021 11:32:54 -0500 Subject: [PATCH 045/452] :zap: Add support for moment types to If Node --- packages/nodes-base/nodes/If.node.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nodes-base/nodes/If.node.ts b/packages/nodes-base/nodes/If.node.ts index 2a2a3bd0bd..5a89f07a10 100644 --- a/packages/nodes-base/nodes/If.node.ts +++ b/packages/nodes-base/nodes/If.node.ts @@ -1,3 +1,4 @@ +import moment = require('moment'); import { IExecuteFunctions } from 'n8n-core'; import { INodeExecutionData, @@ -333,6 +334,8 @@ export class If implements INodeType { returnValue = new Date(value).getTime(); } else if (typeof value === 'number') { returnValue = value; + } if (moment.isMoment(value)) { + returnValue = value.unix(); } if ((value as unknown as object) instanceof Date) { returnValue = (value as unknown as Date).getTime(); } From dc642419df5780aa6adcccf7760a697ccf6d51f7 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 26 Oct 2021 11:32:33 -0500 Subject: [PATCH 046/452] :zap: Make sure that DateTime Node always returns strings --- packages/nodes-base/nodes/DateTime.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/DateTime.node.ts b/packages/nodes-base/nodes/DateTime.node.ts index df586997fd..89e9def2c4 100644 --- a/packages/nodes-base/nodes/DateTime.node.ts +++ b/packages/nodes-base/nodes/DateTime.node.ts @@ -496,7 +496,7 @@ export class DateTime implements INodeType { newItem.binary = item.binary; } - set(newItem, `json.${dataPropertyName}`, newDate); + set(newItem, `json.${dataPropertyName}`, newDate.toISOString()); returnData.push(newItem); } From db738fc824ef695af6b522a86091a9594d7e9e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 27 Oct 2021 01:54:03 +0200 Subject: [PATCH 047/452] :zap: Add workflow name and ID to settings (#2369) * :zap: Add workflow name and ID to settings * :hammer: Refactor to use mapGetters --- packages/editor-ui/src/components/WorkflowSettings.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 77e929b177..04c7d218dc 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -3,7 +3,7 @@ :name="WORKFLOW_SETTINGS_MODAL_KEY" width="65%" maxHeight="80%" - title="Workflow Settings" + :title="`Settings for ${workflowName} (#${workflowId})`" :eventBus="modalBus" :scrollable="true" > @@ -191,6 +191,8 @@ import { WORKFLOW_SETTINGS_MODAL_KEY } from '../constants'; import mixins from 'vue-typed-mixins'; +import { mapGetters } from "vuex"; + export default mixins( externalHooks, genericHelpers, @@ -235,6 +237,11 @@ export default mixins( WORKFLOW_SETTINGS_MODAL_KEY, }; }, + + computed: { + ...mapGetters(['workflowName', 'workflowId']), + }, + async mounted () { if (this.$route.params.name === undefined) { this.$showMessage({ From 15d05c7f0130f6ed2cf3c454d34d18f9cf963477 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 27 Oct 2021 01:58:08 +0200 Subject: [PATCH 048/452] :zap: Fixed ignore response code flag to work properly with return full response (#2370) --- packages/core/src/NodeExecuteFunctions.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 9aa1cf3e8d..6ef54f37ac 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -438,17 +438,17 @@ async function proxyRequestToAxios( } }) .catch((error) => { - if (configObject.simple === true && error.response) { - resolve({ - body: error.response.data, - headers: error.response.headers, - statusCode: error.response.status, - statusMessage: error.response.statusText, - }); - return; - } if (configObject.simple === false && error.response) { - resolve(error.response.data); + if (configObject.resolveWithFullResponse) { + resolve({ + body: error.response.data, + headers: error.response.headers, + statusCode: error.response.status, + statusMessage: error.response.statusText, + }); + } else { + resolve(error.response.data); + } return; } From 3e1fb3e0c9f7d2588ef64fc77b0ac90159e39b3e Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 26 Oct 2021 23:45:26 -0400 Subject: [PATCH 049/452] :zap: Add search filters to contacts and companies (AgileCRM) (#2373) * Added search options for the AgileCRM node * Adjusting AgileCRM getAll operation (using Magento 2 node as a reference) * :zap: Small improvements to #2238 Co-authored-by: Valentina --- .../nodes/AgileCrm/AgileCrm.node.ts | 105 ++++-- .../nodes/AgileCrm/CompanyDescription.ts | 277 +++++++++++++++- .../nodes/AgileCrm/ContactDescription.ts | 311 ++++++++++++++++-- .../nodes/AgileCrm/DealDescription.ts | 2 - .../nodes/AgileCrm/FilterInterface.ts | 19 ++ .../nodes/AgileCrm/GenericFunctions.ts | 86 ++++- 6 files changed, 745 insertions(+), 55 deletions(-) create mode 100644 packages/nodes-base/nodes/AgileCrm/FilterInterface.ts diff --git a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts index c3401f7d86..a31fdb2164 100644 --- a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts +++ b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts @@ -1,4 +1,7 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { + IExecuteFunctions, +} from 'n8n-core'; + import { IDataObject, INodeExecutionData, @@ -22,16 +25,34 @@ import { dealOperations } from './DealDescription'; -import { IContact, IContactUpdate } from './ContactInterface'; -import { agileCrmApiRequest, agileCrmApiRequestUpdate, validateJSON } from './GenericFunctions'; -import { IDeal } from './DealInterface'; +import { + IContact, + IContactUpdate, +} from './ContactInterface'; +import { + agileCrmApiRequest, agileCrmApiRequestAllItems, + agileCrmApiRequestUpdate, + getFilterRules, + simplifyResponse, + validateJSON, +} from './GenericFunctions'; + +import { + IDeal, +} from './DealInterface'; + +import { + IFilter, + ISearchConditions, +} from './FilterInterface'; export class AgileCrm implements INodeType { description: INodeTypeDescription = { displayName: 'Agile CRM', name: 'agileCrm', icon: 'file:agilecrm.png', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', group: ['transform'], version: 1, description: 'Consume Agile CRM API', @@ -86,7 +107,6 @@ export class AgileCrm implements INodeType { }; - async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); @@ -113,26 +133,59 @@ export class AgileCrm implements INodeType { responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {}); } else if (operation === 'getAll') { - const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const simple = this.getNodeParameter('simple', 0) as boolean; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + const filterType = this.getNodeParameter('filterType', i) as string; + const sort = this.getNodeParameter('options.sort.sort', i, {}) as { direction: string, field: string }; + const body: IDataObject = {}; + const filterJson: IFilter = {}; + let contactType = ''; if (resource === 'contact') { - if (returnAll) { - const endpoint = 'api/contacts'; - responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); - } else { - const limit = this.getNodeParameter('limit', i) as number; - const endpoint = `api/contacts?page_size=${limit}`; - responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); - } + contactType = 'PERSON'; } else { - if (returnAll) { - const endpoint = 'api/contacts/companies/list'; - responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {}); + contactType = 'COMPANY'; + } + filterJson.contact_type = contactType; + + if (filterType === 'manual') { + const conditions = this.getNodeParameter('filters.conditions', i, []) as ISearchConditions[]; + const matchType = this.getNodeParameter('matchType', i) as string; + let rules; + if (conditions.length !== 0) { + rules = getFilterRules(conditions, matchType); + Object.assign(filterJson, rules); } else { - const limit = this.getNodeParameter('limit', i) as number; - const endpoint = `api/contacts/companies/list?page_size=${limit}`; - responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {}); + throw new NodeOperationError(this.getNode(), 'At least one condition must be added.'); } + } else if (filterType === 'json') { + const filterJsonRules = this.getNodeParameter('filterJson', i) as string; + if (validateJSON(filterJsonRules) !== undefined) { + Object.assign(filterJson, JSON.parse(filterJsonRules) as IFilter); + } else { + throw new NodeOperationError(this.getNode(), 'Filter (JSON) must be a valid json'); + } + } + body.filterJson = JSON.stringify(filterJson); + + if (sort) { + if (sort.direction === 'ASC') { + body.global_sort_key = sort.field; + } else if (sort.direction === 'DESC') { + body.global_sort_key = `-${sort.field}`; + } + } + + if (returnAll) { + body.page_size = 100; + responseData = await agileCrmApiRequestAllItems.call(this, 'POST', `api/filters/filter/dynamic-filter`, body, undefined, undefined, true); + } else { + body.page_size = this.getNodeParameter('limit', 0) as number; + responseData = await agileCrmApiRequest.call(this, 'POST', `api/filters/filter/dynamic-filter`, body, undefined, undefined, true); + } + + if (simple) { + responseData = simplifyResponse(responseData); } } else if (operation === 'create') { @@ -461,15 +514,15 @@ export class AgileCrm implements INodeType { responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {}); } else if (operation === 'getAll') { - const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + const endpoint = 'api/opportunity'; if (returnAll) { - const endpoint = 'api/opportunity'; - responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + const limit = 100; + responseData = await agileCrmApiRequestAllItems.call(this, 'GET', endpoint, undefined, { page_size: limit }); } else { - const limit = this.getNodeParameter('limit', i) as number; - const endpoint = `api/opportunity?page_size=${limit}`; - responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {}); + const limit = this.getNodeParameter('limit', 0) as number; + responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, undefined, { page_size: limit }); } } else if (operation === 'create') { diff --git a/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts b/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts index da5eeef238..6b888cf4ee 100644 --- a/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts +++ b/packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts @@ -1,6 +1,7 @@ import { INodeProperties, } from 'n8n-workflow'; + export const companyOperations = [ { displayName: 'Operation', @@ -44,6 +45,7 @@ export const companyOperations = [ description: 'The operation to perform.', }, ] as INodeProperties[]; + export const companyFields = [ /* -------------------------------------------------------------------------- */ /* company:get */ @@ -91,7 +93,6 @@ export const companyFields = [ displayName: 'Limit', name: 'limit', type: 'number', - default: 20, displayOptions: { show: { resource: [ @@ -105,7 +106,280 @@ export const companyFields = [ ], }, }, + default: 20, + description: 'Number of results to fetch.', }, + + { + displayName: 'Filter', + name: 'filterType', + type: 'options', + options: [ + { + name: 'None', + value: 'none', + }, + { + name: 'Build Manually', + value: 'manual', + }, + { + name: 'JSON', + value: 'json', + }, + ], + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + }, + }, + default: 'none', + }, + { + displayName: 'Must Match', + name: 'matchType', + type: 'options', + options: [ + { + name: 'Any filter', + value: 'anyFilter', + }, + { + name: 'All Filters', + value: 'allFilters', + }, + ], + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: 'anyFilter', + }, + { + displayName: 'Simplify Response', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Return a simplified version of the response instead of the raw data.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: '', + placeholder: 'Add Condition', + options: [ + { + displayName: 'Conditions', + name: 'conditions', + values: [ + { + displayName: 'Field', + name: 'field', + type: 'string', + default: '', + description: 'Any searchable field.', + }, + { + displayName: 'Condition Type', + name: 'condition_type', + type: 'options', + options: [ + { + name: 'Equals', + value: 'EQUALS', + }, + { + name: 'Not Equal', + value: 'NOTEQUALS', + }, + { + name: 'Last', + value: 'LAST', + }, + { + name: 'Between', + value: 'BETWEEN', + }, + { + name: 'On', + value: 'ON', + }, + { + name: 'Before', + value: 'BEFORE', + }, + { + name: 'After', + value: 'AFTER', + }, + ], + default: 'EQUALS', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + { + displayName: 'Value 2', + name: 'value2', + type: 'string', + displayOptions: { + show: { + condition_type: [ + 'BETWEEN', + ], + }, + }, + default: '', + }, + ], + }, + ], + }, + { + displayName: 'See Agile CRM guide to creating filters', + name: 'jsonNotice', + type: 'notice', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + }, + { + displayName: 'Filters (JSON)', + name: 'filterJson', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + description: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Sort', + name: 'sort', + type: 'fixedCollection', + placeholder: 'Add Sort', + default: [], + options: [ + { + displayName: 'Sort', + name: 'sort', + values: [ + { + displayName: 'Direction', + name: 'direction', + type: 'options', + options: [ + { + name: 'Ascending', + value: 'ASC', + }, + { + name: 'Descending', + value: 'DESC', + }, + ], + default: 'ASC', + description: 'The sorting direction', + }, + { + displayName: 'Field', + name: 'field', + type: 'string', + default: '', + description: `The sorting field`, + }, + ], + }, + ], + }, + ], + }, + /* -------------------------------------------------------------------------- */ /* company:create */ /* -------------------------------------------------------------------------- */ @@ -657,4 +931,5 @@ export const companyFields = [ }, ], }, + ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts index 82cd03afe1..3ccf9b1cb9 100644 --- a/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts +++ b/packages/nodes-base/nodes/AgileCrm/ContactDescription.ts @@ -71,25 +71,7 @@ export const contactFields = [ /* -------------------------------------------------------------------------- */ /* contact:get all */ /* -------------------------------------------------------------------------- */ - { - displayName: 'Limit', - name: 'limit', - type: 'number', - default: 20, - displayOptions: { - show: { - resource: [ - 'contact', - ], - operation: [ - 'getAll', - ], - returnAll: [ - false, - ], - }, - }, - }, + { displayName: 'Return All', name: 'returnAll', @@ -107,6 +89,296 @@ export const contactFields = [ default: false, description: 'If all results should be returned or only up to a given limit.', }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + default: 20, + description: 'Number of results to fetch.', + }, + + { + displayName: 'Filter', + name: 'filterType', + type: 'options', + options: [ + { + name: 'None', + value: 'none', + }, + { + name: 'Build Manually', + value: 'manual', + }, + { + name: 'JSON', + value: 'json', + }, + ], + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + default: 'none', + }, + { + displayName: 'Must Match', + name: 'matchType', + type: 'options', + options: [ + { + name: 'Any filter', + value: 'anyFilter', + }, + { + name: 'All Filters', + value: 'allFilters', + }, + ], + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: 'anyFilter', + }, + { + displayName: 'Simplify Response', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Return a simplified version of the response instead of the raw data.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: '', + placeholder: 'Add Condition', + options: [ + { + displayName: 'Conditions', + name: 'conditions', + values: [ + { + displayName: 'Field', + name: 'field', + type: 'string', + default: '', + description: 'Any searchable field.', + }, + { + displayName: 'Condition Type', + name: 'condition_type', + type: 'options', + options: [ + { + name: 'Equals', + value: 'EQUALS', + }, + { + name: 'Not Equal', + value: 'NOTEQUALS', + }, + { + name: 'Last', + value: 'LAST', + }, + { + name: 'Between', + value: 'BETWEEN', + }, + { + name: 'On', + value: 'ON', + }, + { + name: 'Before', + value: 'BEFORE', + }, + { + name: 'After', + value: 'AFTER', + }, + ], + default: 'EQUALS', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + { + displayName: 'Value 2', + name: 'value2', + type: 'string', + displayOptions: { + show: { + condition_type: [ + 'BETWEEN', + ], + }, + }, + default: '', + }, + ], + }, + ], + }, + { + displayName: 'See Agile CRM guide to creating filters', + name: 'jsonNotice', + type: 'notice', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + }, + { + displayName: 'Filters (JSON)', + name: 'filterJson', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + description: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Sort', + name: 'sort', + type: 'fixedCollection', + placeholder: 'Add Sort', + default: [], + options: [ + { + displayName: 'Sort', + name: 'sort', + values: [ + { + displayName: 'Direction', + name: 'direction', + type: 'options', + options: [ + { + name: 'Ascending', + value: 'ASC', + }, + { + name: 'Descending', + value: 'DESC', + }, + ], + default: 'ASC', + description: 'The sorting direction', + }, + { + displayName: 'Field', + name: 'field', + type: 'string', + default: '', + description: `The sorting field`, + }, + ], + }, + ], + }, + ], + }, /* -------------------------------------------------------------------------- */ /* contact:create */ @@ -988,4 +1260,5 @@ export const contactFields = [ }, ], }, + ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/AgileCrm/DealDescription.ts b/packages/nodes-base/nodes/AgileCrm/DealDescription.ts index 7fb7f55e5f..7dd93643e0 100644 --- a/packages/nodes-base/nodes/AgileCrm/DealDescription.ts +++ b/packages/nodes-base/nodes/AgileCrm/DealDescription.ts @@ -110,8 +110,6 @@ export const dealFields = [ default: false, description: 'If all results should be returned or only up to a given limit.', }, - - /* -------------------------------------------------------------------------- */ /* deal:create */ /* -------------------------------------------------------------------------- */ diff --git a/packages/nodes-base/nodes/AgileCrm/FilterInterface.ts b/packages/nodes-base/nodes/AgileCrm/FilterInterface.ts new file mode 100644 index 0000000000..001dea948a --- /dev/null +++ b/packages/nodes-base/nodes/AgileCrm/FilterInterface.ts @@ -0,0 +1,19 @@ +export interface ISearchConditions { + field?: string; + condition_type?: string; + value?: string; + value2?: string; +} + +export interface IFilterRules { + LHS?: string; + CONDITION?: string; + RHS?: string; + RHS_NEW?: string; +} + +export interface IFilter { + or_rules?: IFilterRules; + rules?: IFilterRules; + contact_type?: string; +} diff --git a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts index 0143b0f624..6c01d3c0b8 100644 --- a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts @@ -1,6 +1,4 @@ -import { - OptionsWithUri -} from 'request'; +import { OptionsWithUri } from 'request'; import { IExecuteFunctions, @@ -10,12 +8,21 @@ import { } from 'n8n-core'; import { - IDataObject, NodeApiError, + IDataObject, + NodeApiError, } from 'n8n-workflow'; -import { IContactUpdate } from './ContactInterface'; +import { + IContactUpdate, +} from './ContactInterface'; -export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any +import { + IFilterRules, + ISearchConditions, +} from './FilterInterface'; + +export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string, sendAsForm?: boolean): Promise { // tslint:disable-line:no-any const credentials = await this.getCredentials('agileCrmApi'); const options: OptionsWithUri = { @@ -27,12 +34,18 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction username: credentials!.email as string, password: credentials!.apiKey as string, }, + qs: query, uri: uri || `https://${credentials!.subdomain}.agilecrm.com/dev/${endpoint}`, json: true, }; + // To send the request as 'content-type': 'application/x-www-form-urlencoded' add form to options instead of body + if(sendAsForm) { + options.form = body; + } // Only add Body property if method not GET or DELETE to avoid 400 response - if (method !== 'GET' && method !== 'DELETE') { + // And when not sending a form + else if (method !== 'GET' && method !== 'DELETE') { options.body = body; } @@ -41,7 +54,30 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction } catch (error) { throw new NodeApiError(this.getNode(), error); } +} +export async function agileCrmApiRequestAllItems(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, + method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, sendAsForm?: boolean): Promise { // tslint:disable-line:no-any + // https://github.com/agilecrm/rest-api#11-listing-contacts- + + const returnData: IDataObject[] = []; + let responseData; + do { + responseData = await agileCrmApiRequest.call(this, method, resource, body, query, uri, sendAsForm); + if (responseData.length !== 0) { + returnData.push.apply(returnData, responseData); + if (sendAsForm) { + body.cursor = responseData[responseData.length-1].cursor; + } else { + query.cursor = responseData[responseData.length-1].cursor; + } + } + } while ( + responseData.length !== 0 && + responseData[responseData.length-1].hasOwnProperty('cursor') + ); + + return returnData; } export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any @@ -131,3 +167,39 @@ export function validateJSON(json: string | undefined): any { // tslint:disable- } return result; } + +export function getFilterRules(conditions: ISearchConditions[], matchType: string): IDataObject { // tslint:disable-line:no-any + const rules = []; + + for (const key in conditions) { + if (conditions.hasOwnProperty(key)) { + const searchConditions: ISearchConditions = conditions[key] as ISearchConditions; + const rule: IFilterRules = { + LHS: searchConditions.field, + CONDITION: searchConditions.condition_type, + RHS: searchConditions.value as string, + RHS_NEW: searchConditions.value2 as string, + }; + rules.push(rule); + } + } + + if (matchType === 'anyFilter') { + return { + or_rules: rules, + }; + } + else { + return { + rules, + }; + } +} + +export function simplifyResponse(records: [{ id: string, properties: [{ name: string, value: string }] } ]) { + const results = []; + for (const record of records) { + results.push(record.properties.reduce((obj, value) => Object.assign(obj, { [`${value.name}`]: value.value }), { id: record.id })); + } + return results; +} From 171f5a458cce96923f5fb4b46ab19d262cf60478 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Wed, 27 Oct 2021 21:55:37 +0200 Subject: [PATCH 050/452] :zap: Update parameter inputs to be multi-line (#2299) * introduce analytics * add user survey backend * add user survey backend * set answers on survey submit Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> * change name to personalization * lint Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> * N8n 2495 add personalization modal (#2280) * update modals * add onboarding modal * implement questions * introduce analytics * simplify impl * implement survey handling * add personalized cateogry * update modal behavior * add thank you view * handle empty cases * rename modal * standarize modal names * update image, add tags to headings * remove unused file * remove unused interfaces * clean up footer spacing * introduce analytics * refactor to fix bug * update endpoint * set min height * update stories * update naming from questions to survey * remove spacing after core categories * fix bug in logic * sort nodes * rename types * merge with be * rename userSurvey * clean up rest api * use constants for keys * use survey keys * clean up types * move personalization to its own file Co-authored-by: ahsan-virani * update parameter inputs to be multiline * update spacing * Survey new options (#2300) * split up options * fix quotes * remove unused import * refactor node credentials * add user created workflow event (#2301) * update multi params * simplify env vars * fix versionCli on FE * update personalization env * clean up node detail settings * fix event User opened Credentials panel * fix font sizes across modals * clean up input spacing * fix select modal spacing * increase spacing * fix input copy * fix webhook, tab spacing, retry button * fix button sizes * fix button size * add mini xlarge sizes * fix webhook spacing * fix nodes panel event * fix workflow id in workflow execute event * improve telemetry error logging * fix config and stop process events * add flush call on n8n stop * ready for release * fix input error highlighting * revert change * update toggle spacing * fix delete positioning * keep tooltip while focused * set strict size * increase left spacing * fix sort icons * remove unnessary margin * clean unused functionality * remove unnessary css * remove duplicate tracking * only show tooltip when hovering over label * update credentials section * use includes Co-authored-by: ahsan-virani Co-authored-by: Jan Oberhauser --- .../components/N8nButton/Button.stories.js | 8 +- .../src/components/N8nButton/Button.vue | 9 +- .../src/components/N8nButton/index.d.ts | 3 - .../src/components/N8nIcon/Icon.vue | 44 ++--- .../components/N8nIconButton/IconButton.vue | 11 +- .../src/components/N8nIconButton/index.d.ts | 3 - .../components/N8nInputLabel/InputLabel.vue | 96 +++++++++-- .../src/components/N8nText/Text.vue | 27 ++- .../src/components/component.d.ts | 2 +- packages/design-system/theme/src/button.scss | 22 +++ packages/design-system/theme/src/dialog.scss | 2 - packages/design-system/theme/src/tabs.scss | 2 +- packages/editor-ui/src/components/About.vue | 1 + .../editor-ui/src/components/CodeEdit.vue | 20 +-- .../src/components/CollectionParameter.vue | 10 +- .../editor-ui/src/components/CopyInput.vue | 1 + .../CredentialEdit/CredentialEdit.vue | 2 +- .../CredentialEdit/CredentialInfo.vue | 14 +- .../src/components/CredentialsList.vue | 4 +- .../editor-ui/src/components/DataDisplay.vue | 2 +- .../src/components/ExecutionsList.vue | 12 +- .../src/components/ExpressionEdit.vue | 1 + .../components/FixedCollectionParameter.vue | 68 ++++---- .../src/components/MultipleParameter.vue | 74 ++++----- .../src/components/NodeCredentials.vue | 156 +++++++----------- .../editor-ui/src/components/NodeSettings.vue | 26 ++- .../editor-ui/src/components/NodeWebhooks.vue | 20 +-- .../src/components/ParameterInput.vue | 17 +- .../src/components/ParameterInputExpanded.vue | 14 +- .../src/components/ParameterInputFull.vue | 94 +++-------- .../src/components/ParameterInputList.vue | 87 +++++----- packages/editor-ui/src/components/RunData.vue | 23 +-- .../editor-ui/src/components/TextEdit.vue | 20 +-- .../src/components/WorkflowSettings.vue | 1 + packages/editor-ui/src/components/helpers.ts | 6 - 35 files changed, 443 insertions(+), 459 deletions(-) diff --git a/packages/design-system/src/components/N8nButton/Button.stories.js b/packages/design-system/src/components/N8nButton/Button.stories.js index 504ea1af38..4958f85de5 100644 --- a/packages/design-system/src/components/N8nButton/Button.stories.js +++ b/packages/design-system/src/components/N8nButton/Button.stories.js @@ -18,7 +18,7 @@ export default { size: { control: { type: 'select', - options: ['small', 'medium', 'large'], + options: ['mini', 'small', 'medium', 'large', 'xlarge'], }, }, loading: { @@ -31,12 +31,6 @@ export default { type: 'text', }, }, - iconSize: { - control: { - type: 'select', - options: ['small', 'medium', 'large'], - }, - }, circle: { control: { type: 'boolean', diff --git a/packages/design-system/src/components/N8nButton/Button.vue b/packages/design-system/src/components/N8nButton/Button.vue index 2827b8f8b7..ff8d78b407 100644 --- a/packages/design-system/src/components/N8nButton/Button.vue +++ b/packages/design-system/src/components/N8nButton/Button.vue @@ -16,13 +16,13 @@ {{ props.label }} @@ -58,7 +58,7 @@ export default { type: String, default: 'medium', validator: (value: string): boolean => - ['small', 'medium', 'large'].indexOf(value) !== -1, + ['mini', 'small', 'medium', 'large', 'xlarge'].indexOf(value) !== -1, }, loading: { type: Boolean, @@ -71,9 +71,6 @@ export default { icon: { type: String, }, - iconSize: { - type: String, - }, round: { type: Boolean, default: true, diff --git a/packages/design-system/src/components/N8nButton/index.d.ts b/packages/design-system/src/components/N8nButton/index.d.ts index b70b0b8418..ad6c9e62b9 100644 --- a/packages/design-system/src/components/N8nButton/index.d.ts +++ b/packages/design-system/src/components/N8nButton/index.d.ts @@ -35,9 +35,6 @@ export declare class N8nButton extends N8nComponent { /** Button icon, accepts an icon name of font awesome icon component */ icon: string; - /** Size of icon */ - iconSize: N8nComponentSize; - /** Full width */ fullWidth: boolean; diff --git a/packages/design-system/src/components/N8nIcon/Icon.vue b/packages/design-system/src/components/N8nIcon/Icon.vue index 52457db0ac..8ffe65edec 100644 --- a/packages/design-system/src/components/N8nIcon/Icon.vue +++ b/packages/design-system/src/components/N8nIcon/Icon.vue @@ -1,19 +1,27 @@ + diff --git a/packages/design-system/src/components/N8nIconButton/IconButton.vue b/packages/design-system/src/components/N8nIconButton/IconButton.vue index 6064ca4a53..513c829f00 100644 --- a/packages/design-system/src/components/N8nIconButton/IconButton.vue +++ b/packages/design-system/src/components/N8nIconButton/IconButton.vue @@ -2,11 +2,10 @@ import N8nButton from '../N8nButton'; -const iconSizeMap = { - large: 'medium', - xlarge: 'large', -}; - export default { name: 'n8n-icon-button', components: { @@ -36,8 +30,6 @@ export default { size: { type: String, default: 'medium', - validator: (value: string): boolean => - ['small', 'medium', 'large', 'xlarge'].indexOf(value) !== -1, }, loading: { type: Boolean, @@ -55,6 +47,5 @@ export default { type: String, }, }, - iconSizeMap, }; diff --git a/packages/design-system/src/components/N8nIconButton/index.d.ts b/packages/design-system/src/components/N8nIconButton/index.d.ts index d939a7e040..46ab70c572 100644 --- a/packages/design-system/src/components/N8nIconButton/index.d.ts +++ b/packages/design-system/src/components/N8nIconButton/index.d.ts @@ -12,9 +12,6 @@ export declare class N8nIconButton extends N8nComponent { /** Button size */ size: N8nComponentSize | 'xlarge'; - /** icon size */ - iconSize: N8nComponentSize; - /** Determine whether it's loading */ loading: boolean; diff --git a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue index a5213ca1aa..a3bc71899a 100644 --- a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue +++ b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue @@ -1,13 +1,13 @@