From c73a5f76dc81f60971b962de2142d3829950a443 Mon Sep 17 00:00:00 2001 From: Oliver Trajceski Date: Thu, 28 Oct 2021 17:59:09 +0200 Subject: [PATCH 01/86] :bug: Rework expression for renaming node for dotted expressions (#2380) --- packages/workflow/src/Workflow.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 7152cbac7..860eaea0d 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -415,8 +415,7 @@ export class Workflow { const currentNameEscaped = currentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); parameterValue = parameterValue.replace( - // eslint-disable-next-line no-useless-escape - new RegExp(`(\\$node(\.|\\["|\\[\'))${currentNameEscaped}((\s/g|"\\]|\'\\]))`, 'g'), + new RegExp(`(\\$node(\\.|\\["|\\['))${currentNameEscaped}((\\.|"\\]|'\\]))`, 'g'), `$1${newName}$3`, ); } From c97ceba86d074ebcc1c7566e19280e9d7dcb03c8 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Thu, 28 Oct 2021 18:07:09 +0200 Subject: [PATCH 02/86] :bug: Fixed the way proxies are declared with axios (#2384) --- packages/core/src/NodeExecuteFunctions.ts | 60 ++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 6ef54f37a..b34f2ed02 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -71,7 +71,7 @@ import { fromBuffer } from 'file-type'; import { lookup } from 'mime-types'; import axios, { AxiosProxyConfig, AxiosRequestConfig, Method } from 'axios'; -import { URLSearchParams } from 'url'; +import { URL, URLSearchParams } from 'url'; // eslint-disable-next-line import/no-cycle import { BINARY_ENCODING, @@ -338,7 +338,63 @@ async function parseRequestObject(requestObject: IDataObject) { } if (requestObject.proxy !== undefined) { - axiosConfig.proxy = requestObject.proxy as AxiosProxyConfig; + // try our best to parse the url provided. + if (typeof requestObject.proxy === 'string') { + try { + const url = new URL(requestObject.proxy); + axiosConfig.proxy = { + host: url.hostname, + port: parseInt(url.port, 10), + protocol: url.protocol, + }; + if (!url.port) { + // Sets port to a default if not informed + if (url.protocol === 'http') { + axiosConfig.proxy.port = 80; + } else if (url.protocol === 'https') { + axiosConfig.proxy.port = 443; + } + } + if (url.username || url.password) { + axiosConfig.proxy.auth = { + username: url.username, + password: url.password, + }; + } + } catch (error) { + // Not a valid URL. We will try to simply parse stuff + // such as user:pass@host:port without protocol (we'll assume http) + if (requestObject.proxy.includes('@')) { + const [userpass, hostport] = requestObject.proxy.split('@'); + const [username, password] = userpass.split(':'); + const [hostname, port] = hostport.split(':'); + axiosConfig.proxy = { + host: hostname, + port: parseInt(port, 10), + protocol: 'http', + auth: { + username, + password, + }, + }; + } else if (requestObject.proxy.includes(':')) { + const [hostname, port] = requestObject.proxy.split(':'); + axiosConfig.proxy = { + host: hostname, + port: parseInt(port, 10), + protocol: 'http', + }; + } else { + axiosConfig.proxy = { + host: requestObject.proxy, + port: 80, + protocol: 'http', + }; + } + } + } else { + axiosConfig.proxy = requestObject.proxy as AxiosProxyConfig; + } } if (requestObject.encoding === null) { From e39678b54f18c424175ef0c0f8f37c55981b790b Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Thu, 28 Oct 2021 18:09:25 +0200 Subject: [PATCH 03/86] :bug: Fixed url params serializing for OAuth1 requests (#2381) --- packages/core/src/NodeExecuteFunctions.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index b34f2ed02..d6dc5da8a 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -86,6 +86,12 @@ import { axios.defaults.timeout = 300000; // Prevent axios from adding x-form-www-urlencoded headers by default axios.defaults.headers.post = {}; +axios.defaults.paramsSerializer = (params) => { + if (params instanceof URLSearchParams) { + return params.toString(); + } + return stringify(params, { arrayFormat: 'indices' }); +}; const requestPromiseWithDefaults = requestPromise.defaults({ timeout: 300000, // 5 minutes @@ -413,6 +419,7 @@ async function parseRequestObject(requestObject: IDataObject) { if ( requestObject.json !== false && axiosConfig.data !== undefined && + axiosConfig.data !== '' && !(axiosConfig.data instanceof Buffer) && !allHeaders.some((headerKey) => headerKey.toLowerCase() === 'content-type') ) { @@ -462,6 +469,11 @@ async function proxyRequestToAxios( axiosConfig = Object.assign(axiosConfig, await parseRequestObject(configObject)); + Logger.debug('Proxying request to axios', { + originalConfig: configObject, + parsedConfig: axiosConfig, + }); + return new Promise((resolve, reject) => { axios(axiosConfig) .then((response) => { From a798c6c0f6bce3feab4113412c081c445e1ff6e3 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 28 Oct 2021 20:00:25 -0400 Subject: [PATCH 04/86] :sparkles: Add Microsoft Dynamics CRM Node (#2292) * :sparkles: Microsoft Dynamics CRM * :zap: Improvements * :zap: Improvements * :zap: Minor improvements Co-authored-by: Jan Oberhauser --- .../MicrosoftDynamicsOAuth2Api.credentials.ts | 30 ++ .../Microsoft/Dynamics/GenericFunctions.ts | 502 ++++++++++++++++++ .../Dynamics/MicrosoftDynamicsCrm.node.ts | 249 +++++++++ .../descriptions/AccountDescription.ts | 274 ++++++++++ .../Microsoft/Dynamics/descriptions/index.ts | 1 + .../nodes/Microsoft/Dynamics/dynamicsCrm.svg | 1 + packages/nodes-base/package.json | 2 + 7 files changed, 1059 insertions(+) create mode 100644 packages/nodes-base/credentials/MicrosoftDynamicsOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/AccountDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/index.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/dynamicsCrm.svg diff --git a/packages/nodes-base/credentials/MicrosoftDynamicsOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftDynamicsOAuth2Api.credentials.ts new file mode 100644 index 000000000..f94722ee2 --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftDynamicsOAuth2Api.credentials.ts @@ -0,0 +1,30 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class MicrosoftDynamicsOAuth2Api implements ICredentialType { + name = 'microsoftDynamicsOAuth2Api'; + extends = [ + 'microsoftOAuth2Api', + ]; + displayName = 'Microsoft Dynamics OAuth2 API'; + documentationUrl = 'microsoft'; + properties: INodeProperties[] = [ + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Subdomain', + name: 'subdomain', + type: 'string', + required: true, + placeholder: 'organization', + default: '', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden', + default: '=openid offline_access https://{{$self.subdomain}}.crm.dynamics.com/.default', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts new file mode 100644 index 000000000..006d7bd74 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts @@ -0,0 +1,502 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodePropertyOptions, + NodeApiError, +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credenitals = await this.getCredentials('microsoftDynamicsOAuth2Api') as { domain: string }; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + 'accept': 'application/json', + 'Prefer': 'return=representation', + }, + method, + body, + qs, + uri: uri || `https://${credenitals.subdomain}.crm.dynamics.com/api/data/v9.2${resource}`, + json: true, + }; + + try { + if (Object.keys(option).length !== 0) { + options = Object.assign({}, options, option); + } + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'microsoftDynamicsOAuth2Api', options, { property: 'id_token' }); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function getPicklistOptions(this: ILoadOptionsFunctions, entityName: string, attributeName: string): Promise { + const returnData: INodePropertyOptions[] = []; + const endpoint = `/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options)`; + const { OptionSet: { Options: options } } = await microsoftApiRequest.call(this, 'GET', endpoint); + for (const option of options) { + returnData.push({ + name: option.Label.UserLocalizedLabel.Label, + value: option.Value, + }); + } + return returnData; +} + +export async function getEntityFields(this: ILoadOptionsFunctions, entityName: string): Promise { + const endpoint = `/EntityDefinitions(LogicalName='${entityName}')/Attributes`; + const { value } = await microsoftApiRequest.call(this, 'GET', endpoint); + return value; +} + +export function adjustAddresses(addresses: [{ [key: string]: string }]) { + // tslint:disable-next-line: no-any + const results: { [key: string]: any } = {}; + for (const [index, address] of addresses.entries()) { + for (const key of Object.keys(address)) { + if (address[key] !== '') { + results[`address${index + 1}_${key}`] = address[key]; + } + } + } + return results; +} + +export function getAccountFields() { + return [ + { + displayName: 'Account Category', + name: 'accountcategorycode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAccountCategories', + }, + default: '', + description: 'Category to indicate whether the customer account is standard or preferred', + }, + { + displayName: 'Account Rating', + name: 'accountratingcode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAccountRatingCodes', + }, + default: '', + }, + { + displayName: 'Address', + name: 'addresses', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Address Field', + options: [ + { + displayName: 'Address Fields', + name: 'address', + values: [ + { + displayName: 'Address Type', + name: 'addresstypecode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getAddressTypes', + }, + default: '', + }, + { + displayName: 'Line1', + name: 'line1', + type: 'string', + default: '', + }, + { + displayName: 'Line2', + name: 'line2', + type: 'string', + default: '', + }, + { + displayName: 'Line3', + name: 'line3', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State or Province', + name: 'stateorprovince', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Postalcode', + name: 'postalcode', + type: 'string', + default: '', + }, + { + displayName: 'Primary Contact Name', + name: 'primarycontactname', + type: 'string', + default: '', + }, + { + displayName: 'Telephone1', + name: 'telephone1', + type: 'string', + default: '', + }, + { + displayName: 'Telephone2', + name: 'telephone2', + type: 'string', + default: '', + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Business Type', + name: 'businesstypecode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBusinessTypes', + }, + default: '', + description: 'The legal designation or other business type of the account for contracts or reporting purposes', + }, + { + displayName: 'Customer Size', + name: 'customersizecode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomerSizeCodes', + }, + default: '', + }, + { + displayName: 'Customer Type', + name: 'customertypecode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomerTypeCodes', + }, + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Additional information to describe the account, such as an excerpt from the company’s website', + }, + { + displayName: 'Email Address 1', + name: 'emailaddress1', + type: 'string', + default: '', + description: 'The primary email address for the account', + }, + { + displayName: 'Email Address 2', + name: 'emailaddress2', + type: 'string', + default: '', + description: 'The secondary email address for the account', + }, + { + displayName: 'Email Address 3', + name: 'emailaddress3', + type: 'string', + default: '', + description: 'Alternate email address for the account', + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'FTP site URL', + name: 'ftpsiteurl', + type: 'string', + default: '', + description: 'URL for the account’s FTP site to enable users to access data and share documents', + }, + { + displayName: 'Industry', + name: 'industrycode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getIndustryCodes', + }, + default: '', + description: 'The account’s primary industry for use in marketing segmentation and demographic analysis', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + '/resource': [ + 'account', + ], + '/operation': [ + 'update', + ], + }, + }, + description: 'Company o business name', + }, + { + displayName: 'Credit Limit', + name: 'creditlimit', + type: 'number', + default: '', + description: 'Credit limit of the account. This is a useful reference when you address invoice and accounting issues with the customer', + }, + { + displayName: 'Number Of Employees', + name: 'numberofemployees', + type: 'number', + default: 0, + description: 'Number of employees that work at the account for use in marketing segmentation and demographic analysis', + }, + { + displayName: 'Payment Terms', + name: 'paymenttermscode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPaymentTermsCodes', + }, + default: '', + description: 'The payment terms to indicate when the customer needs to pay the total amount', + }, + { + displayName: 'Preferred Appointment Day', + name: 'preferredappointmentdaycode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPreferredAppointmentDayCodes', + }, + default: '', + description: '', + }, + { + displayName: 'Preferred Appointment Time', + name: 'preferredappointmenttimecode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPreferredAppointmentTimeCodes', + }, + default: '', + description: '', + }, + { + displayName: 'Preferred Contact Method', + name: 'preferredcontactmethodcode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPreferredContactMethodCodes', + }, + default: '', + description: '', + }, + { + displayName: 'Primary Satori ID', + name: 'primarysatoriid', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'Primary Twitter ID', + name: 'primarytwitterid', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'Revenue', + name: 'revenue', + type: 'number', + default: '', + description: 'The annual revenue for the account, used as an indicator in financial performance analysis', + }, + { + displayName: 'Shares Outstanding', + name: 'sharesoutstanding', + type: 'number', + default: '', + description: 'The number of shares available to the public for the account. This number is used as an indicator in financial performance analysis', + }, + { + displayName: 'Shipping Method', + name: 'shippingmethodcode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getShippingMethodCodes', + }, + default: '', + description: 'Shipping method for deliveries sent to the account’s address to designate the preferred carrier or other delivery option', + }, + { + displayName: 'SIC', + name: 'sic', + type: 'string', + default: '', + description: 'The Standard Industrial Classification (SIC) code that indicates the account’s primary industry of business, for use in marketing segmentation and demographic analysis', + }, + { + displayName: 'Stage ID', + name: 'stageid', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'Stock Exchange', + name: 'stockexchange', + type: 'string', + default: '', + description: 'The stock exchange at which the account is listed to track their stock and financial performance of the company', + }, + { + displayName: 'Telephone 1', + name: 'telephone1', + type: 'string', + default: '', + description: 'The main phone number for this account', + }, + { + displayName: 'Telephone 2', + name: 'telephone2', + type: 'string', + default: '', + description: 'The second phone number for this account', + }, + { + displayName: 'Telephone 3', + name: 'telephone3', + type: 'string', + default: '', + description: 'The third phone number for this account', + }, + { + displayName: 'Territory', + name: 'territorycode', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTerritoryCodes', + }, + default: '', + description: 'Region or territory for the account for use in segmentation and analysis', + }, + { + displayName: 'Ticker Symbol', + name: 'tickersymbol', + type: 'string', + default: '', + description: 'Type the stock exchange symbol for the account to track financial performance of the company. You can click the code entered in this field to access the latest trading information from MSN Money', + }, + { + displayName: 'Website URL', + name: 'websiteurl', + type: 'string', + default: '', + description: 'The account’s website URL to get quick details about the company profile', + }, + { + displayName: 'Yomi Name', + name: 'yominame', + type: 'string', + default: '', + description: 'The phonetic spelling of the company name, if specified in Japanese, to make sure the name is pronounced correctly in phone calls and other communications', + }, + ]; +} + +export const sort = (a: { name: string }, b: { name: string }) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; +}; + +export interface IField { + IsRetrievable: boolean; + LogicalName: string; + IsSearchable: string; + IsValidODataAttribute: string; + IsValidForRead: string; + CanBeSecuredForRead: string; + AttributeType: string; + IsSortableEnabled: { + Value: boolean, + }; + DisplayName: { + UserLocalizedLabel: { + Label: string + } + }; +} diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.ts b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.ts new file mode 100644 index 000000000..675e9f953 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.ts @@ -0,0 +1,249 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + adjustAddresses, + getEntityFields, + getPicklistOptions, + IField, + microsoftApiRequest, + microsoftApiRequestAllItems, + sort, +} from './GenericFunctions'; + +import { + accountFields, + accountOperations, +} from './descriptions'; + +export class MicrosoftDynamicsCrm implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft Dynamics CRM', + name: 'microsoftDynamicsCrm', + icon: 'file:dynamicsCrm.svg', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft Dynamics CRM API', + defaults: { + name: 'Microsoft Dynamics CRM', + color: '#000000', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftDynamicsOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Account', + value: 'account', + }, + ], + default: 'account', + description: 'The resource to operate on', + }, + ...accountOperations, + ...accountFields, + ], + }; + + methods = { + loadOptions: { + async getAccountCategories(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'accountcategorycode'); + }, + async getAccountRatingCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'accountratingcode'); + }, + async getAddressTypes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'address1_addresstypecode'); + }, + async getBusinessTypes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'businesstypecode'); + }, + async getCustomerSizeCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'customersizecode'); + }, + async getCustomerTypeCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'customertypecode'); + }, + async getIndustryCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'industrycode'); + }, + async getPaymentTermsCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'paymenttermscode'); + }, + async getPreferredAppointmentDayCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'preferredappointmentdaycode'); + }, + async getPreferredAppointmentTimeCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'preferredappointmenttimecode'); + }, + async getPreferredContactMethodCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'preferredcontactmethodcode'); + }, + async getShippingMethodCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'shippingmethodcode'); + }, + async getTerritoryCodes(this: ILoadOptionsFunctions): Promise { + return await getPicklistOptions.call(this, 'account', 'territorycode'); + }, + async getAccountFields(this: ILoadOptionsFunctions): Promise { + const fields = await getEntityFields.call(this, 'account'); + const isSelectable = (field: IField) => (field.IsValidForRead && field.CanBeSecuredForRead && field.IsValidODataAttribute && field.LogicalName !== 'slaid'); + return fields.filter(isSelectable).filter(field => field.DisplayName.UserLocalizedLabel?.Label).map(field => ({ name: field.DisplayName.UserLocalizedLabel.Label, value: field.LogicalName })).sort(sort); + }, + async getExpandableAccountFields(this: ILoadOptionsFunctions): Promise { + const fields = await getEntityFields.call(this, 'account'); + const isSelectable = (field: IField) => (field.IsValidForRead && field.CanBeSecuredForRead && field.IsValidODataAttribute && field.AttributeType === 'Lookup' && field.LogicalName !== 'slaid'); + return fields.filter(isSelectable).map(field => ({ name: field.DisplayName.UserLocalizedLabel.Label, value: field.LogicalName })).sort(sort); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < length; i++) { + try { + if (resource === 'account') { + //https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/create-entity-web-api + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + // tslint:disable-next-line: no-any + const additionalFields = this.getNodeParameter('additionalFields', i) as { addresses: { address: [{ [key: string]: any }] } }; + const options = this.getNodeParameter('options', i) as { returnFields: string[] }; + + const body = { + name, + ...additionalFields, + }; + + if (body?.addresses?.address) { + Object.assign(body, adjustAddresses(body.addresses.address)); + //@ts-ignore + delete body?.addresses; + } + + if (options.returnFields) { + options.returnFields.push('accountid'); + qs['$select'] = options.returnFields.join(','); + } else { + qs['$select'] = 'accountid'; + } + + responseData = await microsoftApiRequest.call(this, 'POST', `/accounts`, body, qs); + } + + if (operation === 'delete') { + //https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/update-delete-entities-using-web-api#basic-delete + const accountId = this.getNodeParameter('accountId', i) as string; + await microsoftApiRequest.call(this, 'DELETE', `/accounts(${accountId})`, {}, qs); + responseData = { success: true }; + } + + if (operation === 'get') { + //https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/retrieve-entity-using-web-api + const accountId = this.getNodeParameter('accountId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.returnFields) { + qs['$select'] = (options.returnFields as string[]).join(','); + } + if (options.expandFields) { + qs['$expand'] = (options.expandFields as string[]).join(','); + } + responseData = await microsoftApiRequest.call(this, 'GET', `/accounts(${accountId})`, {}, qs); + } + + if (operation === 'getAll') { + //https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/query-data-web-api + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (options.returnFields) { + qs['$select'] = (options.returnFields as string[]).join(','); + } + if (options.expandFields) { + qs['$expand'] = (options.expandFields as string[]).join(','); + } + if (filters.query) { + qs['$filter'] = filters.query as string; + } + if (returnAll) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/accounts`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', 0) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/accounts`, {}, qs); + responseData = responseData.value; + } + } + + if (operation === 'update') { + const accountId = this.getNodeParameter('accountId', i) as string; + // tslint:disable-next-line: no-any + const updateFields = this.getNodeParameter('updateFields', i) as { addresses: { address: [{ [key: string]: any }] } }; + const options = this.getNodeParameter('options', i) as { returnFields: string[] }; + + const body = { + ...updateFields, + }; + + if (body?.addresses?.address) { + Object.assign(body, adjustAddresses(body.addresses.address)); + //@ts-ignore + delete body?.addresses; + } + + if (options.returnFields) { + options.returnFields.push('accountid'); + qs['$select'] = options.returnFields.join(','); + } else { + qs['$select'] = 'accountid'; + } + + responseData = await microsoftApiRequest.call(this, 'PATCH', `/accounts(${accountId})`, body, qs); + } + } + + if (Array.isArray(responseData)) { + returnData.push(...responseData); + } else { + returnData.push(responseData 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/Microsoft/Dynamics/descriptions/AccountDescription.ts b/packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/AccountDescription.ts new file mode 100644 index 000000000..40bb6b51c --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/AccountDescription.ts @@ -0,0 +1,274 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + getAccountFields, +} from '../GenericFunctions'; + +export const accountOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'account', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + description: 'Operation to perform', + }, +] as INodeProperties[]; + +export const accountFields = [ + // ---------------------------------------- + // account:create + // ---------------------------------------- + { + displayName: 'Name', + name: 'name', + description: 'Company or business name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + ...getAccountFields(), + ], + }, + // ---------------------------------------- + // account:get + // ---------------------------------------- + { + displayName: 'Account ID', + name: 'accountId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'delete', + 'get', + 'update', + ], + }, + }, + }, + // ---------------------------------------- + // account:getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'getAll', + ], + }, + }, + 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: [ + 'account', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'get', + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Return Fields', + name: 'returnFields', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getAccountFields', + }, + default: '', + }, + { + displayName: 'Expand Fields', + name: 'expandFields', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getExpandableAccountFields', + }, + default: '', + }, + ], + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + description: 'Query to filter the results. Check filters', + }, + ], + }, + + // ---------------------------------------- + // account:update + // ---------------------------------------- + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + ...getAccountFields(), + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'create', + 'update', + ], + }, + }, + options: [ + { + displayName: 'Return Fields', + name: 'returnFields', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getAccountFields', + }, + default: '', + description: 'Fields the response will include', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/index.ts b/packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/index.ts new file mode 100644 index 000000000..6bd434939 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/descriptions/index.ts @@ -0,0 +1 @@ +export * from './AccountDescription'; \ No newline at end of file diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/dynamicsCrm.svg b/packages/nodes-base/nodes/Microsoft/Dynamics/dynamicsCrm.svg new file mode 100644 index 000000000..7df712843 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/dynamicsCrm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c2135d5ed..71c29f513 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -172,6 +172,7 @@ "dist/credentials/MediumApi.credentials.js", "dist/credentials/MediumOAuth2Api.credentials.js", "dist/credentials/MessageBirdApi.credentials.js", + "dist/credentials/MicrosoftDynamicsOAuth2Api.credentials.js", "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", @@ -486,6 +487,7 @@ "dist/nodes/Medium/Medium.node.js", "dist/nodes/Merge.node.js", "dist/nodes/MessageBird/MessageBird.node.js", + "dist/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.js", "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", "dist/nodes/Microsoft/Outlook/MicrosoftOutlook.node.js", From 8ca388f1689d2f70eb3c98370a5c54906030ab17 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 28 Oct 2021 19:08:32 -0500 Subject: [PATCH 05/86] :shirt: Fix lint issue --- .../nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts index 006d7bd74..67800133c 100644 --- a/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/GenericFunctions.ts @@ -15,7 +15,7 @@ import { } from 'n8n-workflow'; export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const credenitals = await this.getCredentials('microsoftDynamicsOAuth2Api') as { domain: string }; + const credenitals = await this.getCredentials('microsoftDynamicsOAuth2Api') as { subdomain: string }; let options: OptionsWithUri = { headers: { From 89fee87a88f27230cab9a3a9dbdfd882a44e39ad Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:13:50 +0000 Subject: [PATCH 06/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@0.?= =?UTF-8?q?74.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 61d915a59..a86acf16d 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.73.0", + "version": "0.74.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 95fd11dac2bbc3e582e2c326f600a52b3406e695 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:01 +0000 Subject: [PATCH 07/86] :arrow_up: Set n8n-workflow@0.74.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 88bfc1170..f3f611161 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.73.0", + "n8n-workflow": "~0.74.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From 783d48e3b11fa30bac94253b37ab328d92c2ca67 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:01 +0000 Subject: [PATCH 08/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.91.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 f3f611161..853d39847 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.90.0", + "version": "0.91.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 3bfea67086c5d5e8244d701d244b09d8c60dac50 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:10 +0000 Subject: [PATCH 09/86] :arrow_up: Set n8n-core@0.91.0 and n8n-workflow@0.74.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 93145b731..75dff9d53 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.90.0", - "n8n-workflow": "~0.73.0", + "n8n-core": "~0.91.0", + "n8n-workflow": "~0.74.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From 562871bac5c33806700b16cfab362a3c216b6875 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:11 +0000 Subject: [PATCH 10/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?31.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 75dff9d53..4d45c2831 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.30.0", + "version": "0.31.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 0e02d13ba57dbcba6fd0dbc1a44863335611e0f3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:20 +0000 Subject: [PATCH 11/86] :arrow_up: Set n8n-core@0.91.0 and n8n-workflow@0.74.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 71c29f513..453bdd28a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -667,7 +667,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.73.0", + "n8n-workflow": "~0.74.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -707,7 +707,7 @@ "mssql": "^6.2.0", "mysql2": "~2.3.0", "node-ssh": "^12.0.0", - "n8n-core": "~0.90.0", + "n8n-core": "~0.91.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", "pg": "^8.3.0", From 55e9d15daa12da43ab4574c08d2db257909ad008 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:14:20 +0000 Subject: [PATCH 12/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.143.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 453bdd28a..2760b6129 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.142.0", + "version": "0.143.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From b9b666ee55d35b853f41bba6de0d23762764ebce Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:15:03 +0000 Subject: [PATCH 13/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-design-syst?= =?UTF-8?q?em@0.6.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 13fff0210..7a1b8c79a 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "0.5.0", + "version": "0.6.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", "author": { From a6a40d8be46e2f26aaaa8b8da7ca2d1c9e4cede2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:15:11 +0000 Subject: [PATCH 14/86] :arrow_up: Set n8n-design-system@0.6.0 and n8n-workflow@0.74.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 2f381b574..2c89590d9 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.5.0", + "n8n-design-system": "~0.6.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.73.0", + "n8n-workflow": "~0.74.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From a7a6e77598fc93c7fec4d3fb94c7261e09ef1dec Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:15:11 +0000 Subject: [PATCH 15/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.114.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 2c89590d9..60e7485c8 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.113.0", + "version": "0.114.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 59b58b32453c7bf9d781c04ac40697d5b4073395 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:15:47 +0000 Subject: [PATCH 16/86] :arrow_up: Set n8n-core@0.91.0, n8n-editor-ui@0.114.0, n8n-nodes-base@0.143.0 and n8n-workflow@0.74.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 2ab8191e7..a5f8004e1 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.90.0", - "n8n-editor-ui": "~0.113.0", - "n8n-nodes-base": "~0.142.0", - "n8n-workflow": "~0.73.0", + "n8n-core": "~0.91.0", + "n8n-editor-ui": "~0.114.0", + "n8n-nodes-base": "~0.143.0", + "n8n-workflow": "~0.74.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From ebdd86a5f5f5c86d7b8647ada464dcc1bf45e0f1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 29 Oct 2021 00:15:48 +0000 Subject: [PATCH 17/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.146.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 a5f8004e1..4b0d0526d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.145.0", + "version": "0.146.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 2a164cab6d0c1aaa9227f0bff905239324cdc5c9 Mon Sep 17 00:00:00 2001 From: Ahsan Virani Date: Wed, 3 Nov 2021 10:42:54 +0100 Subject: [PATCH 18/86] add anonymous ID everytime (#2398) --- packages/cli/src/telemetry/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index fb38ed257..d350c6d8b 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -119,6 +119,7 @@ export class Telemetry { this.client.identify( { userId: this.instanceId, + anonymousId: '000000000000', traits: { ...traits, instanceId: this.instanceId, @@ -138,6 +139,7 @@ export class Telemetry { this.client.track( { userId: this.instanceId, + anonymousId: '000000000000', event: eventName, properties, }, From 4f9aee14b59f219074d8bf57de3eb33565752699 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 3 Nov 2021 09:02:20 -0600 Subject: [PATCH 19/86] :sparkles: Add Local File Trigger Node (#2375) * :sparkles: Add File System Watch Trigger Node * :zap: Improvements --- .../nodes-base/nodes/LocalFileTrigger.node.ts | 219 ++++++++++++++++++ packages/nodes-base/package.json | 2 + 2 files changed, 221 insertions(+) create mode 100644 packages/nodes-base/nodes/LocalFileTrigger.node.ts diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.ts b/packages/nodes-base/nodes/LocalFileTrigger.node.ts new file mode 100644 index 000000000..812a3695e --- /dev/null +++ b/packages/nodes-base/nodes/LocalFileTrigger.node.ts @@ -0,0 +1,219 @@ +import { ITriggerFunctions } from 'n8n-core'; +import { + IDataObject, + INodeType, + INodeTypeDescription, + ITriggerResponse, +} from 'n8n-workflow'; + +import { watch } from 'chokidar'; + + +export class LocalFileTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Local File Trigger', + name: 'localFileTrigger', + icon: 'fa:folder-open', + group: ['trigger'], + version: 1, + subtitle: '=Path: {{$parameter["path"]}}', + description: 'Triggers a workflow on file system changes', + defaults: { + name: 'Local File Trigger', + color: '#404040', + }, + inputs: [], + outputs: ['main'], + properties: [ + { + displayName: 'Trigger on', + name: 'triggerOn', + type: 'options', + options: [ + { + name: 'Changes to a Specific File', + value: 'file', + }, + { + name: 'Changes Involving a Specific Folder', + value: 'folder', + }, + ], + required: true, + default: '', + }, + { + displayName: 'File to Watch', + name: 'path', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'file', + ], + }, + }, + default: '', + placeholder: '/data/invoices/1.pdf', + }, + { + displayName: 'Folder to Watch', + name: 'path', + type: 'string', + displayOptions: { + show: { + triggerOn: [ + 'folder', + ], + }, + }, + default: '', + placeholder: '/data/invoices', + }, + { + displayName: 'Watch for', + name: 'events', + type: 'multiOptions', + displayOptions: { + show: { + triggerOn: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'File Added', + value: 'add', + description: 'Triggers whenever a new file was added', + }, + { + name: 'File Changed', + value: 'change', + description: 'Triggers whenever a file was changed', + }, + { + name: 'File Deleted', + value: 'unlink', + description: 'Triggers whenever a file was deleted', + }, + { + name: 'Folder Added', + value: 'addDir', + description: 'Triggers whenever a new folder was added', + }, + { + name: 'Folder Deleted', + value: 'unlinkDir', + description: 'Triggers whenever a folder was deleted', + }, + ], + required: true, + default: [], + description: 'The events to listen to', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Linked Files/Folders', + name: 'followSymlinks', + type: 'boolean', + default: true, + description: 'When activated, linked files/folders will also be watched (this includes symlinks, aliases on MacOS and shortcuts on Windows). Otherwise only the links themselves will be monitored).', + }, + { + displayName: 'Ignore', + name: 'ignored', + type: 'string', + default: '', + placeholder: '**/*.txt', + description: 'Files or paths to ignore. The whole path is tested, not just the filename. Supports Anymatch- syntax.', + }, + { + displayName: 'Max Folder Depth', + name: 'depth', + type: 'options', + options: [ + { + name: 'Unlimited', + value: -1, + }, + { + name: '5 Levels Down', + value: 5, + }, + { + name: '4 Levels Down', + value: 4, + }, + { + name: '3 Levels Down', + value: 3, + }, + { + name: '2 Levels Down', + value: 2, + }, + { + name: '1 Levels Down', + value: 1, + }, + { + name: 'Top Folder Only', + value: 0, + }, + ], + default: -1, + description: 'How deep into the folder structure to watch for changes', + }, + ], + }, + + ], + }; + + + async trigger(this: ITriggerFunctions): Promise { + const triggerOn = this.getNodeParameter('triggerOn') as string; + const path = this.getNodeParameter('path') as string; + const options = this.getNodeParameter('options', {}) as IDataObject; + + let events: string[]; + if (triggerOn === 'file') { + events = [ 'change' ]; + } else { + events = this.getNodeParameter('events', []) as string[]; + } + + const watcher = watch(path, { + ignored: options.ignored, + persistent: true, + ignoreInitial: true, + followSymlinks: options.followSymlinks === undefined ? true : options.followSymlinks as boolean, + depth: [-1, undefined].includes(options.depth as number) ? undefined : options.depth as number, + }); + + const executeTrigger = (event: string, path: string) => { + this.emit([this.helpers.returnJsonArray([{ event,path }])]); + }; + + for (const eventName of events) { + watcher.on(eventName, path => executeTrigger(eventName, path)); + } + + function closeFunction() { + return watcher.close(); + } + + return { + closeFunction, + }; + + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2760b6129..238e4226c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -469,6 +469,7 @@ "dist/nodes/Line/Line.node.js", "dist/nodes/LingvaNex/LingvaNex.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", + "dist/nodes/LocalFileTrigger.node.js", "dist/nodes/Magento/Magento2.node.js", "dist/nodes/MailerLite/MailerLite.node.js", "dist/nodes/MailerLite/MailerLiteTrigger.node.js", @@ -665,6 +666,7 @@ "@types/tmp": "^0.2.0", "@types/uuid": "^8.3.0", "@types/xml2js": "^0.4.3", + "chokidar": "^3.5.2", "gulp": "^4.0.0", "jest": "^26.4.2", "n8n-workflow": "~0.74.0", From 0877f485d994e29c047f6dee771bdfe181a64b10 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> Date: Wed, 3 Nov 2021 16:12:48 +0100 Subject: [PATCH 20/86] :zap: Run migration in chunks (#2393) --- .../cli/src/databases/MigrationHelpers.ts | 39 +++ ...1630451444017-UpdateWorkflowCredentials.ts | 259 +++++++++++------ ...1630419189837-UpdateWorkflowCredentials.ts | 248 +++++++++++------ ...1630330987096-UpdateWorkflowCredentials.ts | 261 ++++++++++++------ 4 files changed, 560 insertions(+), 247 deletions(-) create mode 100644 packages/cli/src/databases/MigrationHelpers.ts diff --git a/packages/cli/src/databases/MigrationHelpers.ts b/packages/cli/src/databases/MigrationHelpers.ts new file mode 100644 index 000000000..7db121bd8 --- /dev/null +++ b/packages/cli/src/databases/MigrationHelpers.ts @@ -0,0 +1,39 @@ +import { QueryRunner } from 'typeorm'; + +export class MigrationHelpers { + queryRunner: QueryRunner; + + constructor(queryRunner: QueryRunner) { + this.queryRunner = queryRunner; + } + + // runs an operation sequential on chunks of a query that returns a potentially large Array. + /* eslint-disable no-await-in-loop */ + async runChunked( + query: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + operation: (results: any[]) => Promise, + limit = 100, + ): Promise { + let offset = 0; + let chunkedQuery: string; + let chunkedQueryResults: unknown[]; + + do { + chunkedQuery = this.chunkQuery(query, limit, offset); + chunkedQueryResults = (await this.queryRunner.query(chunkedQuery)) as unknown[]; + // pass a copy to prevent errors from mutation + await operation([...chunkedQueryResults]); + offset += limit; + } while (chunkedQueryResults.length === limit); + } + /* eslint-enable no-await-in-loop */ + + private chunkQuery(query: string, limit: number, offset = 0): string { + return ` + ${query} + LIMIT ${limit} + OFFSET ${offset} + `; + } +} diff --git a/packages/cli/src/databases/mysqldb/migrations/1630451444017-UpdateWorkflowCredentials.ts b/packages/cli/src/databases/mysqldb/migrations/1630451444017-UpdateWorkflowCredentials.ts index 0012ee0aa..0061052c2 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1630451444017-UpdateWorkflowCredentials.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1630451444017-UpdateWorkflowCredentials.ts @@ -1,5 +1,6 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import config = require('../../../../config'); +import { MigrationHelpers } from '../../MigrationHelpers'; // replacing the credentials in workflows and execution // `nodeType: name` changes to `nodeType: { id, name }` @@ -8,58 +9,100 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac name = 'UpdateWorkflowCredentials1630451444017'; public async up(queryRunner: QueryRunner): Promise { + console.log('Start migration', this.name); + console.time(this.name); const tablePrefix = config.get('database.tablePrefix'); + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM ${tablePrefix}credentials_entity `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM ${tablePrefix}workflow_entity - `); + `; // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = workflow.nodes; - let credentialsUpdated = false; - // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, name] of allNodeCredentials) { - if (typeof name === 'string') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.name === name && credentials.type === type, - ); - node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; - credentialsUpdated = true; + await helpers.runChunked(workflowsQuery, (workflows) => { + workflows.forEach(async (workflow) => { + const nodes = workflow.nodes; + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; + credentialsUpdated = true; + } } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}workflow_entity + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE ${tablePrefix}workflow_entity - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, workflowData FROM ${tablePrefix}execution_entity WHERE waitTill IS NOT NULL AND finished = 0 - `); + `; + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + waitingExecutions.forEach(async (execution) => { + const data = execution.workflowData; + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}execution_entity + SET workflowData = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, workflowData @@ -68,8 +111,8 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac ORDER BY startedAt DESC LIMIT 200 `); - - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = execution.workflowData; let credentialsUpdated = false; // @ts-ignore @@ -78,7 +121,6 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac const allNodeCredentials = Object.entries(node.credentials); for (const [type, name] of allNodeCredentials) { if (typeof name === 'string') { - // @ts-ignore const matchingCredentials = credentialsEntities.find( // @ts-ignore (credentials) => credentials.name === name && credentials.type === type, @@ -92,77 +134,124 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac if (credentialsUpdated) { const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( ` - UPDATE ${tablePrefix}execution_entity - SET workflowData = :data - WHERE id = '${execution.id}' - `, + UPDATE ${tablePrefix}execution_entity + SET workflowData = :data + WHERE id = '${execution.id}' + `, { data: JSON.stringify(data) }, {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); + console.timeEnd(this.name); } public async down(queryRunner: QueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM ${tablePrefix}credentials_entity `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM ${tablePrefix}workflow_entity - `); + `; // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = workflow.nodes; - let credentialsUpdated = false; - // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, creds] of allNodeCredentials) { - if (typeof creds === 'object') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, - ); - if (matchingCredentials) { - node.credentials[type] = matchingCredentials.name; - } else { - // @ts-ignore - node.credentials[type] = creds.name; + await helpers.runChunked(workflowsQuery, (workflows) => { + workflows.forEach(async (workflow) => { + const nodes = workflow.nodes; + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; } - credentialsUpdated = true; } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}workflow_entity + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE ${tablePrefix}workflow_entity - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, workflowData FROM ${tablePrefix}execution_entity WHERE waitTill IS NOT NULL AND finished = 0 - `); + `; + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + waitingExecutions.forEach(async (execution) => { + const data = execution.workflowData; + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { + // @ts-ignore + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}execution_entity + SET workflowData = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, workflowData @@ -171,8 +260,8 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac ORDER BY startedAt DESC LIMIT 200 `); - - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = execution.workflowData; let credentialsUpdated = false; // @ts-ignore @@ -200,15 +289,15 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac if (credentialsUpdated) { const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( ` - UPDATE ${tablePrefix}execution_entity - SET workflowData = :data - WHERE id = '${execution.id}' - `, + UPDATE ${tablePrefix}execution_entity + SET workflowData = :data + WHERE id = '${execution.id}' + `, { data: JSON.stringify(data) }, {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); } diff --git a/packages/cli/src/databases/postgresdb/migrations/1630419189837-UpdateWorkflowCredentials.ts b/packages/cli/src/databases/postgresdb/migrations/1630419189837-UpdateWorkflowCredentials.ts index 357d7c297..ad3e44f0e 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1630419189837-UpdateWorkflowCredentials.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1630419189837-UpdateWorkflowCredentials.ts @@ -1,5 +1,6 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import config = require('../../../../config'); +import { MigrationHelpers } from '../../MigrationHelpers'; // replacing the credentials in workflows and execution // `nodeType: name` changes to `nodeType: { id, name }` @@ -8,62 +9,104 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac name = 'UpdateWorkflowCredentials1630419189837'; public async up(queryRunner: QueryRunner): Promise { + console.log('Start migration', this.name); + console.time(this.name); let tablePrefix = config.get('database.tablePrefix'); const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM ${tablePrefix}credentials_entity `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM ${tablePrefix}workflow_entity - `); + `; // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = workflow.nodes; - let credentialsUpdated = false; - // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, name] of allNodeCredentials) { - if (typeof name === 'string') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.name === name && credentials.type === type, - ); - node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; - credentialsUpdated = true; + await helpers.runChunked(workflowsQuery, (workflows) => { + workflows.forEach(async (workflow) => { + const nodes = workflow.nodes; + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; + credentialsUpdated = true; + } } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}workflow_entity + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE ${tablePrefix}workflow_entity - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, "workflowData" FROM ${tablePrefix}execution_entity WHERE "waitTill" IS NOT NULL AND finished = FALSE - `); + `; + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + waitingExecutions.forEach(async (execution) => { + const data = execution.workflowData; + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}execution_entity + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, "workflowData" @@ -73,7 +116,8 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac LIMIT 200 `); - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = execution.workflowData; let credentialsUpdated = false; // @ts-ignore @@ -104,9 +148,10 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); + console.timeEnd(this.name); } public async down(queryRunner: QueryRunner): Promise { @@ -115,62 +160,109 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac if (schema) { tablePrefix = schema + '.' + tablePrefix; } + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM ${tablePrefix}credentials_entity `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM ${tablePrefix}workflow_entity - `); + `; // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = workflow.nodes; - let credentialsUpdated = false; - // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, creds] of allNodeCredentials) { - if (typeof creds === 'object') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( + await helpers.runChunked(workflowsQuery, (workflows) => { + workflows.forEach(async (workflow) => { + const nodes = workflow.nodes; + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, - ); - if (matchingCredentials) { - node.credentials[type] = matchingCredentials.name; - } else { - // @ts-ignore - node.credentials[type] = creds.name; + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; } - credentialsUpdated = true; } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}workflow_entity + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE ${tablePrefix}workflow_entity - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, "workflowData" FROM ${tablePrefix}execution_entity WHERE "waitTill" IS NOT NULL AND finished = FALSE - `); + `; + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + waitingExecutions.forEach(async (execution) => { + const data = execution.workflowData; + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { + // @ts-ignore + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE ${tablePrefix}execution_entity + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, "workflowData" @@ -179,8 +271,8 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac ORDER BY "startedAt" DESC LIMIT 200 `); - - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = execution.workflowData; let credentialsUpdated = false; // @ts-ignore @@ -208,15 +300,15 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac if (credentialsUpdated) { const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( ` - UPDATE ${tablePrefix}execution_entity - SET "workflowData" = :data - WHERE id = '${execution.id}' - `, + UPDATE ${tablePrefix}execution_entity + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, { data: JSON.stringify(data) }, {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); } diff --git a/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts b/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts index f2a6f0a19..147e5e49b 100644 --- a/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts +++ b/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts @@ -1,5 +1,6 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import config = require('../../../../config'); +import { MigrationHelpers } from '../../MigrationHelpers'; // replacing the credentials in workflows and execution // `nodeType: name` changes to `nodeType: { id, name }` @@ -8,58 +9,101 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac name = 'UpdateWorkflowCredentials1630330987096'; public async up(queryRunner: QueryRunner): Promise { + console.log('Start migration', this.name); + console.time(this.name); const tablePrefix = config.get('database.tablePrefix'); + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM "${tablePrefix}credentials_entity" `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM "${tablePrefix}workflow_entity" - `); + `; + // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = JSON.parse(workflow.nodes); - let credentialsUpdated = false; - // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, name] of allNodeCredentials) { - if (typeof name === 'string') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.name === name && credentials.type === type, - ); - node.credentials[type] = { id: matchingCredentials?.id || null, name }; - credentialsUpdated = true; + await helpers.runChunked(workflowsQuery, (workflows) => { + workflows.forEach(async (workflow) => { + const nodes = JSON.parse(workflow.nodes); + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id || null, name }; + credentialsUpdated = true; + } } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE "${tablePrefix}workflow_entity" + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE "${tablePrefix}workflow_entity" - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, "workflowData" FROM "${tablePrefix}execution_entity" WHERE "waitTill" IS NOT NULL AND finished = 0 - `); + `; + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + waitingExecutions.forEach(async (execution) => { + const data = JSON.parse(execution.workflowData); + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, name] of allNodeCredentials) { + if (typeof name === 'string') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.name === name && credentials.type === type, + ); + node.credentials[type] = { id: matchingCredentials?.id || null, name }; + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE "${tablePrefix}execution_entity" + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, "workflowData" @@ -68,8 +112,8 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac ORDER BY "startedAt" DESC LIMIT 200 `); - - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = JSON.parse(execution.workflowData); let credentialsUpdated = false; // @ts-ignore @@ -78,7 +122,6 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac const allNodeCredentials = Object.entries(node.credentials); for (const [type, name] of allNodeCredentials) { if (typeof name === 'string') { - // @ts-ignore const matchingCredentials = credentialsEntities.find( // @ts-ignore (credentials) => credentials.name === name && credentials.type === type, @@ -92,77 +135,127 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac if (credentialsUpdated) { const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( ` - UPDATE "${tablePrefix}execution_entity" - SET "workflowData" = :data - WHERE id = '${execution.id}' - `, + UPDATE "${tablePrefix}execution_entity" + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, { data: JSON.stringify(data) }, {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); + console.timeEnd(this.name); } public async down(queryRunner: QueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); + const helpers = new MigrationHelpers(queryRunner); const credentialsEntities = await queryRunner.query(` SELECT id, name, type FROM "${tablePrefix}credentials_entity" `); - const workflows = await queryRunner.query(` + const workflowsQuery = ` SELECT id, nodes FROM "${tablePrefix}workflow_entity" - `); + `; + // @ts-ignore - workflows.forEach(async (workflow) => { - const nodes = JSON.parse(workflow.nodes); - let credentialsUpdated = false; + await helpers.runChunked(workflowsQuery, (workflows) => { // @ts-ignore - nodes.forEach((node) => { - if (node.credentials) { - const allNodeCredentials = Object.entries(node.credentials); - for (const [type, creds] of allNodeCredentials) { - if (typeof creds === 'object') { - // @ts-ignore - const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, - ); - if (matchingCredentials) { - node.credentials[type] = matchingCredentials.name; - } else { - // @ts-ignore - node.credentials[type] = creds.name; + workflows.forEach(async (workflow) => { + const nodes = JSON.parse(workflow.nodes); + let credentialsUpdated = false; + // @ts-ignore + nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; } - credentialsUpdated = true; } } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE "${tablePrefix}workflow_entity" + SET nodes = :nodes + WHERE id = '${workflow.id}' + `, + { nodes: JSON.stringify(nodes) }, + {}, + ); + + queryRunner.query(updateQuery, updateParams); } }); - if (credentialsUpdated) { - const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( - ` - UPDATE "${tablePrefix}workflow_entity" - SET nodes = :nodes - WHERE id = '${workflow.id}' - `, - { nodes: JSON.stringify(nodes) }, - {}, - ); - - await queryRunner.query(updateQuery, updateParams); - } }); - const waitingExecutions = await queryRunner.query(` + const waitingExecutionsQuery = ` SELECT id, "workflowData" FROM "${tablePrefix}execution_entity" WHERE "waitTill" IS NOT NULL AND finished = 0 - `); + `; + + // @ts-ignore + await helpers.runChunked(waitingExecutionsQuery, (waitingExecutions) => { + // @ts-ignore + waitingExecutions.forEach(async (execution) => { + const data = JSON.parse(execution.workflowData); + let credentialsUpdated = false; + // @ts-ignore + data.nodes.forEach((node) => { + if (node.credentials) { + const allNodeCredentials = Object.entries(node.credentials); + for (const [type, creds] of allNodeCredentials) { + if (typeof creds === 'object') { + const matchingCredentials = credentialsEntities.find( + // @ts-ignore + (credentials) => credentials.id === creds.id && credentials.type === type, + ); + if (matchingCredentials) { + node.credentials[type] = matchingCredentials.name; + } else { + // @ts-ignore + node.credentials[type] = creds.name; + } + credentialsUpdated = true; + } + } + } + }); + if (credentialsUpdated) { + const [updateQuery, updateParams] = + queryRunner.connection.driver.escapeQueryWithParameters( + ` + UPDATE "${tablePrefix}execution_entity" + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, + { data: JSON.stringify(data) }, + {}, + ); + + await queryRunner.query(updateQuery, updateParams); + } + }); + }); const retryableExecutions = await queryRunner.query(` SELECT id, "workflowData" @@ -172,7 +265,8 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac LIMIT 200 `); - [...waitingExecutions, ...retryableExecutions].forEach(async (execution) => { + // @ts-ignore + retryableExecutions.forEach(async (execution) => { const data = JSON.parse(execution.workflowData); let credentialsUpdated = false; // @ts-ignore @@ -181,7 +275,6 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac const allNodeCredentials = Object.entries(node.credentials); for (const [type, creds] of allNodeCredentials) { if (typeof creds === 'object') { - // @ts-ignore const matchingCredentials = credentialsEntities.find( // @ts-ignore (credentials) => credentials.id === creds.id && credentials.type === type, @@ -200,15 +293,15 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac if (credentialsUpdated) { const [updateQuery, updateParams] = queryRunner.connection.driver.escapeQueryWithParameters( ` - UPDATE "${tablePrefix}execution_entity" - SET "workflowData" = :data - WHERE id = '${execution.id}' - `, + UPDATE "${tablePrefix}execution_entity" + SET "workflowData" = :data + WHERE id = '${execution.id}' + `, { data: JSON.stringify(data) }, {}, ); - await queryRunner.query(updateQuery, updateParams); + queryRunner.query(updateQuery, updateParams); } }); } From ffd59ccd3fcbc2496214edb61c9c5030b4c0a3ad Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 15:13:57 +0000 Subject: [PATCH 21/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.144.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 238e4226c..42fc69169 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.143.0", + "version": "0.144.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 91e50105b9513b104d4a216974ecaa41b8a15da9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 15:14:44 +0000 Subject: [PATCH 22/86] :arrow_up: Set n8n-nodes-base@0.144.0 on n8n --- 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 4b0d0526d..bbf7a2733 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -112,7 +112,7 @@ "mysql2": "~2.3.0", "n8n-core": "~0.91.0", "n8n-editor-ui": "~0.114.0", - "n8n-nodes-base": "~0.143.0", + "n8n-nodes-base": "~0.144.0", "n8n-workflow": "~0.74.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 85f6c84301bef27da8346deeb5105bc70aeeb3b0 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 15:14:45 +0000 Subject: [PATCH 23/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.147.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 bbf7a2733..c2e2eeb89 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.146.0", + "version": "0.147.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From b11bde49d16d978a9091178cdce2387398e8c2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 3 Nov 2021 17:02:44 +0100 Subject: [PATCH 24/86] :bug: Switch chokidar to regular dep (#2404) --- packages/nodes-base/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 42fc69169..04b7747d3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -666,7 +666,6 @@ "@types/tmp": "^0.2.0", "@types/uuid": "^8.3.0", "@types/xml2js": "^0.4.3", - "chokidar": "^3.5.2", "gulp": "^4.0.0", "jest": "^26.4.2", "n8n-workflow": "~0.74.0", @@ -684,10 +683,11 @@ "basic-auth": "^2.0.1", "change-case": "^4.1.1", "cheerio": "1.0.0-rc.6", + "chokidar": "^3.5.2", "cron": "~1.7.2", "eventsource": "^1.0.7", - "fflate": "^0.7.0", "fast-glob": "^3.2.5", + "fflate": "^0.7.0", "formidable": "^1.2.1", "get-system-fonts": "^2.0.2", "gm": "^1.23.1", @@ -708,8 +708,8 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.3.0", - "node-ssh": "^12.0.0", "n8n-core": "~0.91.0", + "node-ssh": "^12.0.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", "pg": "^8.3.0", From 38f5b4bd945135f77a6b53ca3d2804a501ec9754 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 10:03:54 -0600 Subject: [PATCH 25/86] :arrow_up: Set n8n-nodes-base@0.144.1 on n8n --- 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 04b7747d3..969fc9971 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.144.0", + "version": "0.144.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From bae45421a8ddb117031ba76216e42a3979a45697 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 10:06:09 -0600 Subject: [PATCH 26/86] :arrow_up: Set n8n-nodes-base@0.144.1 on n8n --- 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 c2e2eeb89..9b2f0eea9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -112,7 +112,7 @@ "mysql2": "~2.3.0", "n8n-core": "~0.91.0", "n8n-editor-ui": "~0.114.0", - "n8n-nodes-base": "~0.144.0", + "n8n-nodes-base": "~0.144.1", "n8n-workflow": "~0.74.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From aaa39876f9159066e55c46fadb95bcfe7efbc506 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 3 Nov 2021 10:06:53 -0600 Subject: [PATCH 27/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.147.1?= 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 9b2f0eea9..f4924adbe 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.147.0", + "version": "0.147.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From a3bfdd380532e68e59ccecb0b012f97fd9c34eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 4 Nov 2021 01:42:57 +0100 Subject: [PATCH 28/86] :bug: Fix Stripe pagination (#2402) * Fix Stripe pagination * Fix displayOptions for type --- .../nodes-base/nodes/Stripe/Stripe.node.ts | 6 ++--- .../Stripe/descriptions/TokenDescription.ts | 10 +++++++++ packages/nodes-base/nodes/Stripe/helpers.ts | 22 ++++++++++++------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/nodes-base/nodes/Stripe/Stripe.node.ts b/packages/nodes-base/nodes/Stripe/Stripe.node.ts index 06eb3577a..6127f1fe1 100644 --- a/packages/nodes-base/nodes/Stripe/Stripe.node.ts +++ b/packages/nodes-base/nodes/Stripe/Stripe.node.ts @@ -255,7 +255,7 @@ export class Stripe implements INodeType { // charge: getAll // ---------------------------------- - responseData = await handleListing.call(this, resource); + responseData = await handleListing.call(this, resource, i); } else if (operation === 'update') { @@ -313,7 +313,7 @@ export class Stripe implements INodeType { // coupon: getAll // ---------------------------------- - responseData = await handleListing.call(this, resource); + responseData = await handleListing.call(this, resource, i); } @@ -374,7 +374,7 @@ export class Stripe implements INodeType { qs.email = filters.email; } - responseData = await handleListing.call(this, resource, qs); + responseData = await handleListing.call(this, resource, i, qs); } else if (operation === 'update') { diff --git a/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts index 591a438dd..ae454bf79 100644 --- a/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts +++ b/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts @@ -43,6 +43,16 @@ export const tokenFields = [ value: 'cardToken', }, ], + displayOptions: { + show: { + resource: [ + 'token', + ], + operation: [ + 'create', + ], + }, + }, }, { displayName: 'Card Number', diff --git a/packages/nodes-base/nodes/Stripe/helpers.ts b/packages/nodes-base/nodes/Stripe/helpers.ts index c7ec812a4..cbf163bb6 100644 --- a/packages/nodes-base/nodes/Stripe/helpers.ts +++ b/packages/nodes-base/nodes/Stripe/helpers.ts @@ -154,19 +154,25 @@ export async function loadResource( export async function handleListing( this: IExecuteFunctions, resource: string, + i: number, qs: IDataObject = {}, ) { + const returnData: IDataObject[] = []; let responseData; - responseData = await stripeApiRequest.call(this, 'GET', `/${resource}s`, qs, {}); - responseData = responseData.data; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const limit = this.getNodeParameter('limit', i, 0) as number; - const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + do { + responseData = await stripeApiRequest.call(this, 'GET', `/${resource}s`, {}, qs); + returnData.push(...responseData.data); - if (!returnAll) { - const limit = this.getNodeParameter('limit', 0) as number; - responseData = responseData.slice(0, limit); - } + if (!returnAll && returnData.length >= limit) { + return returnData.slice(0, limit); + } - return responseData; + qs.starting_after = returnData[returnData.length - 1].id; + } while (responseData.has_more); + + return returnData; } From a5805fb80bb06682a2e27d1bcd253ae3d23285e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Santamar=C3=ADa?= Date: Thu, 4 Nov 2021 01:44:25 +0100 Subject: [PATCH 29/86] :bug: Fix Stripe node multiple metadata values (#2395) --- packages/nodes-base/nodes/Stripe/helpers.ts | 4 +-- .../test/nodes/Stripe/helpers.test.js | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/test/nodes/Stripe/helpers.test.js diff --git a/packages/nodes-base/nodes/Stripe/helpers.ts b/packages/nodes-base/nodes/Stripe/helpers.ts index cbf163bb6..38823eb12 100644 --- a/packages/nodes-base/nodes/Stripe/helpers.ts +++ b/packages/nodes-base/nodes/Stripe/helpers.ts @@ -102,10 +102,10 @@ export function adjustMetadata( ) { if (!fields.metadata || isEmpty(fields.metadata)) return fields; - let adjustedMetadata = {}; + const adjustedMetadata: Record = {}; fields.metadata.metadataProperties.forEach(pair => { - adjustedMetadata = { ...adjustedMetadata, ...pair }; + adjustedMetadata[pair.key] = pair.value; }); return { diff --git a/packages/nodes-base/test/nodes/Stripe/helpers.test.js b/packages/nodes-base/test/nodes/Stripe/helpers.test.js new file mode 100644 index 000000000..a5d480768 --- /dev/null +++ b/packages/nodes-base/test/nodes/Stripe/helpers.test.js @@ -0,0 +1,30 @@ +const helpers = require("../../../nodes/Stripe/helpers"); + +describe('adjustMetadata', () => { + it('it should adjust multiple metadata values', async () => { + const additionalFieldsValues = { + metadata: { + metadataProperties: [ + { + key: "keyA", + value: "valueA" + }, + { + key: "keyB", + value: "valueB" + }, + ], + }, + } + + const adjustedMetadata = helpers.adjustMetadata(additionalFieldsValues) + + const expectedAdjustedMetadata = { + metadata: { + keyA: "valueA", + keyB: "valueB" + } + } + expect(adjustedMetadata).toStrictEqual(expectedAdjustedMetadata) + }); +}); From 0f9edd666dfef6a5fce48278536481ae1a5a98b5 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 3 Nov 2021 20:48:07 -0400 Subject: [PATCH 30/86] :zap: Add password field to customer:create (#2390) --- .../nodes/WooCommerce/descriptions/shared.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/nodes-base/nodes/WooCommerce/descriptions/shared.ts b/packages/nodes-base/nodes/WooCommerce/descriptions/shared.ts index 14e3fa1f9..7800b8554 100644 --- a/packages/nodes-base/nodes/WooCommerce/descriptions/shared.ts +++ b/packages/nodes-base/nodes/WooCommerce/descriptions/shared.ts @@ -118,6 +118,22 @@ const customerUpdateOptions = [ }, ], }, + { + displayName: 'Password', + name: 'password', + type: 'string', + displayOptions: { + show: { + '/resource': [ + 'customer', + ], + '/operation': [ + 'create', + ], + }, + }, + default: '', + }, { displayName: 'Shipping Address', name: 'shipping', From 3971e30affcb04323ec9202e45c281c69e3282c8 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 3 Nov 2021 20:55:04 -0400 Subject: [PATCH 31/86] :sparkles: Add user group resource to Slack Node (#2405) --- .../credentials/SlackOAuth2Api.credentials.ts | 2 + .../nodes/Slack/GenericFunctions.ts | 6 + packages/nodes-base/nodes/Slack/Slack.node.ts | 110 ++++- .../nodes/Slack/UserGroupDescription.ts | 378 ++++++++++++++++++ 4 files changed, 491 insertions(+), 5 deletions(-) create mode 100644 packages/nodes-base/nodes/Slack/UserGroupDescription.ts diff --git a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts index 7c44beeb6..e4aef0c82 100644 --- a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts @@ -15,6 +15,8 @@ const userScopes = [ 'reactions:write', 'stars:read', 'stars:write', + 'usergroups:write', + 'usergroups:read', 'users.profile:read', 'users.profile:write', ]; diff --git a/packages/nodes-base/nodes/Slack/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/GenericFunctions.ts index 3d92cdab2..e9ee77117 100644 --- a/packages/nodes-base/nodes/Slack/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/GenericFunctions.ts @@ -58,6 +58,12 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu } if (response.ok === false) { + if (response.error === 'paid_teams_only') { + throw new NodeOperationError(this.getNode(), `Your current Slack plan does not include the resource '${this.getNodeParameter('resource', 0) as string}'`, { + description: `Hint: Upgrate to the Slack plan that includes the funcionality you want to use.`, + }); + } + throw new NodeOperationError(this.getNode(), 'Slack error response: ' + JSON.stringify(response)); } diff --git a/packages/nodes-base/nodes/Slack/Slack.node.ts b/packages/nodes-base/nodes/Slack/Slack.node.ts index 5213489c7..cd506473d 100644 --- a/packages/nodes-base/nodes/Slack/Slack.node.ts +++ b/packages/nodes-base/nodes/Slack/Slack.node.ts @@ -41,6 +41,11 @@ import { reactionOperations, } from './ReactionDescription'; +import { + userGroupFields, + userGroupOperations, +} from './UserGroupDescription'; + import { userFields, userOperations, @@ -191,6 +196,10 @@ export class Slack implements INodeType { name: 'User', value: 'user', }, + { + name: 'User Group', + value: 'userGroup', + }, { name: 'User Profile', value: 'userProfile', @@ -212,6 +221,8 @@ export class Slack implements INodeType { ...reactionFields, ...userOperations, ...userFields, + ...userGroupOperations, + ...userGroupFields, ...userProfileOperations, ...userProfileFields, ], @@ -295,13 +306,14 @@ export class Slack implements INodeType { try { const response = await this.helpers.request(options); + if (!response.ok) { return { status: 'Error', message: `${response.error}`, }; } - } catch(err) { + } catch (err) { return { status: 'Error', message: `${err.message}`, @@ -414,10 +426,10 @@ export class Slack implements INodeType { qs.inclusive = filters.inclusive as boolean; } if (filters.latest) { - qs.latest = new Date(filters.latest as string).getTime()/1000; + qs.latest = new Date(filters.latest as string).getTime() / 1000; } if (filters.oldest) { - qs.oldest = new Date(filters.oldest as string).getTime()/1000; + qs.oldest = new Date(filters.oldest as string).getTime() / 1000; } if (returnAll === true) { responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.history', {}, qs); @@ -508,10 +520,10 @@ export class Slack implements INodeType { qs.inclusive = filters.inclusive as boolean; } if (filters.latest) { - qs.latest = new Date(filters.latest as string).getTime()/1000; + qs.latest = new Date(filters.latest as string).getTime() / 1000; } if (filters.oldest) { - qs.oldest = new Date(filters.oldest as string).getTime()/1000; + qs.oldest = new Date(filters.oldest as string).getTime() / 1000; } if (returnAll === true) { responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.replies', {}, qs); @@ -1036,6 +1048,94 @@ export class Slack implements INodeType { responseData = await slackApiRequest.call(this, 'GET', '/users.getPresence', {}, qs); } } + if (resource === 'userGroup') { + //https://api.slack.com/methods/usergroups.create + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = { + name, + }; + + Object.assign(body, additionalFields); + + responseData = await slackApiRequest.call(this, 'POST', '/usergroups.create', body, qs); + + responseData = responseData.usergroup; + } + //https://api.slack.com/methods/usergroups.enable + if (operation === 'enable') { + const userGroupId = this.getNodeParameter('userGroupId', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = { + usergroup: userGroupId, + }; + + Object.assign(body, additionalFields); + + responseData = await slackApiRequest.call(this, 'POST', '/usergroups.enable', body, qs); + + responseData = responseData.usergroup; + } + //https://api.slack.com/methods/usergroups.disable + if (operation === 'disable') { + const userGroupId = this.getNodeParameter('userGroupId', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = { + usergroup: userGroupId, + }; + + Object.assign(body, additionalFields); + + responseData = await slackApiRequest.call(this, 'POST', '/usergroups.disable', body, qs); + + responseData = responseData.usergroup; + } + + //https://api.slack.com/methods/usergroups.list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const qs: IDataObject = {}; + + Object.assign(qs, additionalFields); + + responseData = await slackApiRequest.call(this, 'GET', '/usergroups.list', {}, qs); + + responseData = responseData.usergroups; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + + responseData = responseData.slice(0, limit); + } + } + + //https://api.slack.com/methods/usergroups.update + if (operation === 'update') { + const userGroupId = this.getNodeParameter('userGroupId', i) as string; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + const body: IDataObject = { + usergroup: userGroupId, + }; + + Object.assign(body, updateFields); + + responseData = await slackApiRequest.call(this, 'POST', '/usergroups.update', body, qs); + + responseData = responseData.usergroup; + } + } if (resource === 'userProfile') { //https://api.slack.com/methods/users.profile.set if (operation === 'update') { diff --git a/packages/nodes-base/nodes/Slack/UserGroupDescription.ts b/packages/nodes-base/nodes/Slack/UserGroupDescription.ts new file mode 100644 index 000000000..f352e6af2 --- /dev/null +++ b/packages/nodes-base/nodes/Slack/UserGroupDescription.ts @@ -0,0 +1,378 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const userGroupOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a user group', + }, + { + name: 'Disable', + value: 'disable', + description: 'Disable a user group', + }, + { + name: 'Enable', + value: 'enable', + description: 'Enable a user group', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all user groups', + }, + { + name: 'Update', + value: 'update', + description: 'Update a user group', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const userGroupFields = [ + + /* -------------------------------------------------------------------------- */ + /* userGroup:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'userGroup', + ], + }, + }, + required: true, + description: 'A name for the User Group. Must be unique among User Groups.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Channel IDs', + name: 'channelIds', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, + default: [], + description: 'A comma separated string of encoded channel IDs for which the User Group uses as a default.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'A short description of the User Group.', + }, + { + displayName: 'Handle', + name: 'handle', + type: 'string', + default: '', + description: 'A mention handle. Must be unique among channels, users and User Groups.', + }, + { + displayName: 'Include Count', + name: 'include_count', + type: 'boolean', + default: true, + description: 'Include the number of users in each User Group.', + }, + ], + }, + /* ----------------------------------------------------------------------- */ + /* userGroup:disable */ + /* ----------------------------------------------------------------------- */ + { + displayName: 'User Group ID', + name: 'userGroupId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'disable', + ], + resource: [ + 'userGroup', + ], + }, + }, + required: true, + description: 'The encoded ID of the User Group to update.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + operation: [ + 'disable', + ], + }, + }, + options: [ + { + displayName: 'Include Count', + name: 'include_count', + type: 'boolean', + default: true, + description: 'Include the number of users in each User Group.', + }, + ], + }, + /* ----------------------------------------------------------------------- */ + /* userGroup:enable */ + /* ----------------------------------------------------------------------- */ + { + displayName: 'User Group ID', + name: 'userGroupId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'enable', + ], + resource: [ + 'userGroup', + ], + }, + }, + required: true, + description: 'The encoded ID of the User Group to update.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + operation: [ + 'enable', + ], + }, + }, + options: [ + { + displayName: 'Include Count', + name: 'include_count', + type: 'boolean', + default: true, + description: 'Include the number of users in each User Group.', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* userGroup:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'userGroup', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'userGroup', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Include Count', + name: 'include_count', + type: 'boolean', + default: true, + description: 'Include the number of users in each User Group.', + }, + { + displayName: 'Include Disabled', + name: 'include_disabled', + type: 'boolean', + default: true, + description: 'Include disabled User Groups.', + }, + { + displayName: 'Include Users', + name: 'include_users', + type: 'boolean', + default: true, + description: 'Include the list of users for each User Group.', + }, + ], + }, + /* ----------------------------------------------------------------------- */ + /* userGroup:update */ + /* ----------------------------------------------------------------------- */ + { + displayName: 'User Group ID', + name: 'userGroupId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'userGroup', + ], + }, + }, + required: true, + description: 'The encoded ID of the User Group to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'userGroup', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Channel IDs', + name: 'channels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, + default: [], + description: 'A comma separated string of encoded channel IDs for which the User Group uses as a default.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'A short description of the User Group.', + }, + { + displayName: 'Handle', + name: 'handle', + type: 'string', + default: '', + description: 'A mention handle. Must be unique among channels, users and User Groups.', + }, + { + displayName: 'Include Count', + name: 'include_count', + type: 'boolean', + default: true, + description: 'Include the number of users in each User Group.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'A name for the User Group. Must be unique among User Groups.', + }, + ], + }, +] as INodeProperties[]; \ No newline at end of file From 2125beb216e9ceed1549bd4b5e33fbd909c7da99 Mon Sep 17 00:00:00 2001 From: Tom <19203795+that-one-tom@users.noreply.github.com> Date: Thu, 4 Nov 2021 05:47:41 +0100 Subject: [PATCH 32/86] :zap: Add additional fields available through Lemlist API (#2377) --- .../Lemlist/descriptions/LeadDescription.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts b/packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts index 9c4fe039e..d9cb1e531 100644 --- a/packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts +++ b/packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts @@ -124,6 +124,34 @@ export const leadFields = [ default: '', description: 'Last name of the lead to create.', }, + { + displayName: 'Icebreaker', + name: 'icebreaker', + type: 'string', + default: '', + description: 'Icebreaker of the lead to create.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of the lead to create.', + }, + { + displayName: 'Picture URL', + name: 'picture', + type: 'string', + default: '', + description: 'Picture url of the lead to create.', + }, + { + displayName: 'LinkedIn URL', + name: 'linkedinUrl', + type: 'string', + default: '', + description: 'LinkedIn url of the lead to create.', + }, ], }, From f6dae26532a37a8762f2870722412838363da21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 4 Nov 2021 20:59:53 +0100 Subject: [PATCH 33/86] :zap: Add .npmrc to fix build issues (#2243) --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..e9ee3cb4d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true \ No newline at end of file From e84846dcf915387382094acc174aaf65adfa2f90 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 4 Nov 2021 22:21:35 -0400 Subject: [PATCH 34/86] :zap: Add task:update - Todoist (#2409) * :zap: Add task:update * :zap: Minor improvements Co-authored-by: Jan Oberhauser --- .../nodes/Todoist/GenericFunctions.ts | 1 - .../nodes-base/nodes/Todoist/Todoist.node.ts | 129 ++++++++++++++++-- 2 files changed, 121 insertions(+), 9 deletions(-) diff --git a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts index 97665c3c4..bd09118da 100644 --- a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts @@ -44,7 +44,6 @@ export async function todoistApiRequest( //@ts-ignore options.headers['Authorization'] = `Bearer ${credentials.apiKey}`; - return this.helpers.request!(options); } else { //@ts-ignore diff --git a/packages/nodes-base/nodes/Todoist/Todoist.node.ts b/packages/nodes-base/nodes/Todoist/Todoist.node.ts index 4aeca39d8..a4ebc6fe2 100644 --- a/packages/nodes-base/nodes/Todoist/Todoist.node.ts +++ b/packages/nodes-base/nodes/Todoist/Todoist.node.ts @@ -16,7 +16,7 @@ import { } from './GenericFunctions'; interface IBodyCreateTask { - content: string; + content?: string; description?: string; project_id?: number; section_id?: number; @@ -146,6 +146,11 @@ export class Todoist implements INodeType { value: 'reopen', description: 'Reopen a task', }, + { + name: 'Update', + value: 'update', + description: 'Update a task', + }, ], default: 'create', description: 'The operation to perform.', @@ -228,6 +233,7 @@ export class Todoist implements INodeType { 'close', 'get', 'reopen', + 'update', ], }, }, @@ -398,6 +404,76 @@ export class Todoist implements INodeType { }, ], }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + description: 'Task content', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'A description for the task.', + }, + { + displayName: 'Due Date Time', + name: 'dueDateTime', + type: 'dateTime', + default: '', + description: 'Specific date and time in RFC3339 format in UTC.', + }, + { + displayName: 'Due String', + name: 'dueString', + type: 'string', + default: '', + description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + }, + default: [], + required: false, + description: 'Labels', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'number', + typeOptions: { + numberStepSize: 1, + maxValue: 4, + minValue: 1, + }, + default: 1, + description: 'Task priority from 1 (normal) to 4 (urgent).', + }, + ], + }, ], }; @@ -485,33 +561,33 @@ export class Todoist implements INodeType { const projectId = this.getNodeParameter('project', i) as number; const labels = this.getNodeParameter('labels', i) as number[]; const options = this.getNodeParameter('options', i) as IDataObject; - + const body: IBodyCreateTask = { content, project_id: projectId, priority: (options.priority!) ? parseInt(options.priority as string, 10) : 1, }; - + if (options.description) { body.description = options.description as string; } - + if (options.dueDateTime) { body.due_datetime = options.dueDateTime as string; } - + if (options.dueString) { body.due_string = options.dueString as string; } - + if (labels !== undefined && labels.length !== 0) { body.label_ids = labels; } - + if (options.section) { body.section_id = options.section as number; } - + responseData = await todoistApiRequest.call(this, 'POST', '/tasks', body); } if (operation === 'close') { @@ -573,6 +649,43 @@ export class Todoist implements INodeType { responseData = { success: true }; } + + if (operation === 'update') { + //https://developer.todoist.com/rest/v1/#update-a-task + const id = this.getNodeParameter('taskId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + const body: IBodyCreateTask = {}; + + if (updateFields.content) { + body.content = updateFields.content as string; + } + + if (updateFields.priority) { + body.priority = parseInt(updateFields.priority as string, 10); + } + + if (updateFields.description) { + body.description = updateFields.description as string; + } + + if (updateFields.dueDateTime) { + body.due_datetime = updateFields.dueDateTime as string; + } + + if (updateFields.dueString) { + body.due_string = updateFields.dueString as string; + } + + if (updateFields.labels !== undefined && + Array.isArray(updateFields.labels) && + updateFields.labels.length !== 0) { + body.label_ids = updateFields.labels as number[]; + } + + await todoistApiRequest.call(this, 'POST', `/tasks/${id}`, body); + responseData = { success: true }; + } } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); From a46c7f827d94b73326c64e7d45199d0d27498949 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> Date: Fri, 5 Nov 2021 03:23:10 +0100 Subject: [PATCH 35/86] :bug: Fix saving credentials id as string (#2410) --- .../1630330987096-UpdateWorkflowCredentials.ts | 18 +++++++++--------- packages/editor-ui/src/views/NodeView.vue | 9 ++++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts b/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts index 147e5e49b..273f644e4 100644 --- a/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts +++ b/packages/cli/src/databases/sqlite/migrations/1630330987096-UpdateWorkflowCredentials.ts @@ -39,7 +39,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac // @ts-ignore (credentials) => credentials.name === name && credentials.type === type, ); - node.credentials[type] = { id: matchingCredentials?.id || null, name }; + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; credentialsUpdated = true; } } @@ -82,7 +82,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac // @ts-ignore (credentials) => credentials.name === name && credentials.type === type, ); - node.credentials[type] = { id: matchingCredentials?.id || null, name }; + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; credentialsUpdated = true; } } @@ -126,7 +126,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac // @ts-ignore (credentials) => credentials.name === name && credentials.type === type, ); - node.credentials[type] = { id: matchingCredentials?.id || null, name }; + node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name }; credentialsUpdated = true; } } @@ -176,8 +176,8 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac for (const [type, creds] of allNodeCredentials) { if (typeof creds === 'object') { const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, + // @ts-ignore double-equals because creds.id can be string or number + (credentials) => credentials.id == creds.id && credentials.type === type, ); if (matchingCredentials) { node.credentials[type] = matchingCredentials.name; @@ -226,8 +226,8 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac for (const [type, creds] of allNodeCredentials) { if (typeof creds === 'object') { const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, + // @ts-ignore double-equals because creds.id can be string or number + (credentials) => credentials.id == creds.id && credentials.type === type, ); if (matchingCredentials) { node.credentials[type] = matchingCredentials.name; @@ -276,8 +276,8 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac for (const [type, creds] of allNodeCredentials) { if (typeof creds === 'object') { const matchingCredentials = credentialsEntities.find( - // @ts-ignore - (credentials) => credentials.id === creds.id && credentials.type === type, + // @ts-ignore double-equals because creds.id can be string or number + (credentials) => credentials.id == creds.id && credentials.type === type, ); if (matchingCredentials) { node.credentials[type] = matchingCredentials.name; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 82c8cf1d9..3e6abff17 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1917,10 +1917,13 @@ export default mixins( if (nodeCredentials.id) { // Check whether the id is matching with a credential - const credentialsForId = credentialOptions.find((optionData: ICredentialsResponse) => optionData.id === nodeCredentials.id); + const credentialsId = nodeCredentials.id.toString(); // due to a fixed bug in the migration UpdateWorkflowCredentials (just sqlite) we have to cast to string and check later if it has been a number + const credentialsForId = credentialOptions.find((optionData: ICredentialsResponse) => + optionData.id === credentialsId, + ); if (credentialsForId) { - if (credentialsForId.name !== nodeCredentials.name) { - node.credentials![nodeCredentialType].name = credentialsForId.name; + if (credentialsForId.name !== nodeCredentials.name || typeof nodeCredentials.id === 'number') { + node.credentials![nodeCredentialType] = { id: credentialsForId.id, name: credentialsForId.name }; this.credentialsUpdated = true; } return; From cfd797b8adc6684bd8cbedffbd30eed3f2b6cb8f Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 5 Nov 2021 15:45:20 +0100 Subject: [PATCH 36/86] :zap: Add codex files (#2412) --- .../nodes/Aws/Textract/AwsTextract.node.json | 20 ++++++++++++++++ .../Google/Drive/GoogleDriveTrigger.node.json | 20 ++++++++++++++++ .../nodes/LocalFileTrigger.node.json | 24 +++++++++++++++++++ .../Dynamics/MicrosoftDynamicsCrm.node.json | 21 ++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json create mode 100644 packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json create mode 100644 packages/nodes-base/nodes/LocalFileTrigger.node.json create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json diff --git a/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json new file mode 100644 index 000000000..370cfd887 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.awsTextract", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Utility" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/aws" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsTextract/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json new file mode 100644 index 000000000..dc0a632b9 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.googleDriveTrigger", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/google" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.googleDriveTrigger/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.json b/packages/nodes-base/nodes/LocalFileTrigger.node.json new file mode 100644 index 000000000..3ca3c3806 --- /dev/null +++ b/packages/nodes-base/nodes/LocalFileTrigger.node.json @@ -0,0 +1,24 @@ +{ + "node": "n8n-nodes-base.localFileTrigger", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Core Nodes" + ], + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.localFileTrigger/" + } + ] + }, + "alias": [ + "Watch", + "Monitor" + ], + "subcategories": { + "Core Nodes":[ + "Files" + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json new file mode 100644 index 000000000..4773de057 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json @@ -0,0 +1,21 @@ +{ + "node": "n8n-nodes-base.microsoftDynamicsCrm", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Marketing & Content", + "Sales" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/microsoft" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.microsoftDynamicsCrm/" + } + ] + } +} From 35787455ab7da14aae52a54990f87980b9678215 Mon Sep 17 00:00:00 2001 From: nikozila Date: Fri, 5 Nov 2021 18:17:05 +0330 Subject: [PATCH 37/86] :zap: Add hook: workflow.afterCreate (#2407) --- packages/cli/src/Server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index e9de494f4..a3e0f2d1f 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -679,6 +679,7 @@ class App { // @ts-ignore savedWorkflow.id = savedWorkflow.id.toString(); + await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]); void InternalHooksManager.getInstance().onWorkflowCreated(newWorkflow as IWorkflowBase); return savedWorkflow; }, From 3ec52c1875eba4ef87857ad398068e8f95705fe6 Mon Sep 17 00:00:00 2001 From: Michele Paiano Date: Fri, 5 Nov 2021 15:49:31 +0100 Subject: [PATCH 38/86] :bug: Zendesk node: fix user External ID option name (#2392) Incorrect name of the "External ID" option prevents the "external_id" property from being correctly valued on Zendesk. Co-authored-by: MizziMizzi --- packages/nodes-base/nodes/Zendesk/UserDescription.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Zendesk/UserDescription.ts b/packages/nodes-base/nodes/Zendesk/UserDescription.ts index eb7a1c706..f181e5c1b 100644 --- a/packages/nodes-base/nodes/Zendesk/UserDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/UserDescription.ts @@ -130,7 +130,7 @@ export const userFields = [ }, { displayName: 'External ID', - name: 'externalId', + name: 'external_id', type: 'string', default: '', description: 'A unique identifier from another system', @@ -387,7 +387,7 @@ export const userFields = [ }, { displayName: 'External ID', - name: 'externalId', + name: 'external_id', type: 'string', default: '', description: 'A unique identifier from another system', From 70a9f0446e8538ee45c9f67c36818d471b2f314e Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:40:33 +0100 Subject: [PATCH 39/86] :bug: Fix importing unknown types with credentials (#2414) --- packages/editor-ui/src/modules/credentials.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/modules/credentials.ts b/packages/editor-ui/src/modules/credentials.ts index 9f2b70c7c..ac2020360 100644 --- a/packages/editor-ui/src/modules/credentials.ts +++ b/packages/editor-ui/src/modules/credentials.ts @@ -98,7 +98,7 @@ const module: Module = { }, getCredentialsByType: (state: ICredentialsState, getters: any) => { // tslint:disable-line:no-any return (credentialType: string): ICredentialsResponse[] => { - return getters.allCredentialsByType[credentialType]; + return getters.allCredentialsByType[credentialType] || []; }; }, getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any From 7b8d388d17fa35f9a10ce4a5951b515ee7bafc62 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 5 Nov 2021 10:45:51 -0600 Subject: [PATCH 40/86] Add Webhook response node (#2254) * :sparkles: Add Webhook-Response-Node * :zap: Replace callback function with promise * :sparkles: Add support for Bull and binary-data * :sparkles: Add string response option * :zap: Remove some comments * :sparkles: Make more generically possible & fix issue multi call in queue mode * :zap: Fix startup and eslint issues * :zap: Improvements to webhook response node and functionality * :zap: Replace data with more generic type * :zap: Make statusMessage optional * :zap: Change parameter order * :zap: Move Response Code underneath options * :zap: Hide Response Code on Webhook node if mode responseNode got selected * :zap: Minor improvements * :zap: Add missing file and fix lint issue * :zap: Fix some node linting issues * :zap: Apply feedback * :zap: Minor improvements --- packages/cli/commands/worker.ts | 14 +- packages/cli/src/ActiveExecutions.ts | 32 +- packages/cli/src/ActiveWorkflowRunner.ts | 16 +- packages/cli/src/Interfaces.ts | 13 +- packages/cli/src/Queue.ts | 19 +- packages/cli/src/ResponseHelper.ts | 5 + packages/cli/src/Server.ts | 24 +- packages/cli/src/WebhookHelpers.ts | 101 ++++++- packages/cli/src/WebhookServer.ts | 48 ++- packages/cli/src/WorkflowRunner.ts | 37 ++- packages/cli/src/WorkflowRunnerProcess.ts | 11 + packages/core/src/NodeExecuteFunctions.ts | 4 + packages/core/src/index.ts | 1 - packages/core/test/Helpers.ts | 3 +- packages/core/test/WorkflowExecute.test.ts | 12 +- .../nodes-base/nodes/RespondToWebhook.node.ts | 278 ++++++++++++++++++ packages/nodes-base/nodes/Wait.node.ts | 5 + packages/nodes-base/nodes/Webhook.node.ts | 64 ++-- packages/nodes-base/package.json | 1 + .../{core => workflow}/src/DeferredPromise.ts | 0 packages/workflow/src/Interfaces.ts | 17 +- packages/workflow/src/Workflow.ts | 23 +- packages/workflow/src/index.ts | 1 + 23 files changed, 664 insertions(+), 65 deletions(-) create mode 100644 packages/nodes-base/nodes/RespondToWebhook.node.ts rename packages/{core => workflow}/src/DeferredPromise.ts (100%) diff --git a/packages/cli/commands/worker.ts b/packages/cli/commands/worker.ts index 9a06868f3..28a02b9b3 100644 --- a/packages/cli/commands/worker.ts +++ b/packages/cli/commands/worker.ts @@ -12,7 +12,7 @@ import * as PCancelable from 'p-cancelable'; import { Command, flags } from '@oclif/command'; import { UserSettings, WorkflowExecute } from 'n8n-core'; -import { INodeTypes, IRun, Workflow, LoggerProxy } from 'n8n-workflow'; +import { IExecuteResponsePromiseData, INodeTypes, IRun, Workflow, LoggerProxy } from 'n8n-workflow'; import { FindOneOptions } from 'typeorm'; @@ -25,11 +25,13 @@ import { GenericHelpers, IBullJobData, IBullJobResponse, + IBullWebhookResponse, IExecutionFlattedDb, InternalHooksManager, LoadNodesAndCredentials, NodeTypes, ResponseHelper, + WebhookHelpers, WorkflowExecuteAdditionalData, } from '../src'; @@ -172,6 +174,16 @@ export class Worker extends Command { currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string }, ); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + await job.progress({ + executionId: job.data.executionId as string, + response: WebhookHelpers.encodeWebhookResponse(response), + } as IBullWebhookResponse); + }, + ]; + additionalData.executionId = jobData.executionId; let workflowExecute: WorkflowExecute; diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index dac67322c..cd02ebe67 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -5,9 +5,12 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { IRun } from 'n8n-workflow'; - -import { createDeferredPromise } from 'n8n-core'; +import { + createDeferredPromise, + IDeferredPromise, + IExecuteResponsePromiseData, + IRun, +} from 'n8n-workflow'; import { ChildProcess } from 'child_process'; // eslint-disable-next-line import/no-extraneous-dependencies @@ -116,6 +119,28 @@ export class ActiveExecutions { this.activeExecutions[executionId].workflowExecution = workflowExecution; } + attachResponsePromise( + executionId: string, + responsePromise: IDeferredPromise, + ): void { + if (this.activeExecutions[executionId] === undefined) { + throw new Error( + `No active execution with id "${executionId}" got found to attach to workflowExecution to!`, + ); + } + + this.activeExecutions[executionId].responsePromise = responsePromise; + } + + resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void { + if (this.activeExecutions[executionId] === undefined) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this.activeExecutions[executionId].responsePromise?.resolve(response); + } + /** * Remove an active execution * @@ -193,6 +218,7 @@ export class ActiveExecutions { this.activeExecutions[executionId].postExecutePromises.push(waitPromise); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return waitPromise.promise(); } diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 181671c4f..dd8ac09c3 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -12,7 +12,9 @@ import { ActiveWorkflows, NodeExecuteFunctions } from 'n8n-core'; import { + IDeferredPromise, IExecuteData, + IExecuteResponsePromiseData, IGetExecutePollFunctions, IGetExecuteTriggerFunctions, INode, @@ -40,8 +42,6 @@ import { NodeTypes, ResponseHelper, WebhookHelpers, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowExecuteAdditionalData, WorkflowHelpers, WorkflowRunner, @@ -550,6 +550,7 @@ export class ActiveWorkflowRunner { data: INodeExecutionData[][], additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, + responsePromise?: IDeferredPromise, ) { const nodeExecutionStack: IExecuteData[] = [ { @@ -580,7 +581,7 @@ export class ActiveWorkflowRunner { }; const workflowRunner = new WorkflowRunner(); - return workflowRunner.run(runData, true); + return workflowRunner.run(runData, true, undefined, undefined, responsePromise); } /** @@ -641,13 +642,16 @@ export class ActiveWorkflowRunner { mode, activation, ); - returnFunctions.emit = (data: INodeExecutionData[][]): void => { + returnFunctions.emit = ( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ): void => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.debug(`Received trigger for workflow "${workflow.name}"`); WorkflowHelpers.saveStaticData(workflow); // eslint-disable-next-line id-denylist - this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => - console.error(err), + this.runWorkflow(workflowData, node, data, additionalData, mode, responsePromise).catch( + (error) => console.error(error), ); }; return returnFunctions; diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index d5c11a8f3..556aa7449 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -7,19 +7,19 @@ import { ICredentialsEncrypted, ICredentialType, IDataObject, + IDeferredPromise, + IExecuteResponsePromiseData, IRun, IRunData, IRunExecutionData, ITaskData, ITelemetrySettings, IWorkflowBase as IWorkflowBaseWorkflow, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - IWorkflowCredentials, Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; -import { IDeferredPromise, WorkflowExecute } from 'n8n-core'; +import { WorkflowExecute } from 'n8n-core'; // eslint-disable-next-line import/no-extraneous-dependencies import * as PCancelable from 'p-cancelable'; @@ -47,6 +47,11 @@ export interface IBullJobResponse { success: boolean; } +export interface IBullWebhookResponse { + executionId: string; + response: IExecuteResponsePromiseData; +} + export interface ICustomRequest extends Request { parsedUrl: Url | undefined; } @@ -237,6 +242,7 @@ export interface IExecutingWorkflowData { process?: ChildProcess; startedAt: Date; postExecutePromises: Array>; + responsePromise?: IDeferredPromise; workflowExecution?: PCancelable; } @@ -490,6 +496,7 @@ export interface IPushDataConsoleMessage { export interface IResponseCallbackData { data?: IDataObject | IDataObject[]; + headers?: object; noWebhookResponse?: boolean; responseCode?: number; } diff --git a/packages/cli/src/Queue.ts b/packages/cli/src/Queue.ts index 9143c59ee..5d215a2bd 100644 --- a/packages/cli/src/Queue.ts +++ b/packages/cli/src/Queue.ts @@ -1,12 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import * as Bull from 'bull'; import * as config from '../config'; // eslint-disable-next-line import/no-cycle -import { IBullJobData } from './Interfaces'; +import { IBullJobData, IBullWebhookResponse } from './Interfaces'; +// eslint-disable-next-line import/no-cycle +import * as ActiveExecutions from './ActiveExecutions'; +// eslint-disable-next-line import/no-cycle +import * as WebhookHelpers from './WebhookHelpers'; export class Queue { + private activeExecutions: ActiveExecutions.ActiveExecutions; + private jobQueue: Bull.Queue; constructor() { + this.activeExecutions = ActiveExecutions.getInstance(); + const prefix = config.get('queue.bull.prefix') as string; const redisOptions = config.get('queue.bull.redis') as object; // Disabling ready check is necessary as it allows worker to @@ -16,6 +25,14 @@ export class Queue { // More here: https://github.com/OptimalBits/bull/issues/890 // @ts-ignore this.jobQueue = new Bull('jobs', { prefix, redis: redisOptions, enableReadyCheck: false }); + + this.jobQueue.on('global:progress', (jobId, progress: IBullWebhookResponse) => { + this.activeExecutions.resolveResponsePromise( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + progress.executionId, + WebhookHelpers.decodeWebhookResponse(progress.response), + ); + }); } async add(jobData: IBullJobData, jobOptions: object): Promise { diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index f6deb551b..e8430c695 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -72,11 +72,16 @@ export function sendSuccessResponse( data: any, raw?: boolean, responseCode?: number, + responseHeader?: object, ) { if (responseCode !== undefined) { res.status(responseCode); } + if (responseHeader) { + res.header(responseHeader); + } + if (raw === true) { if (typeof data === 'string') { res.send(data); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a3e0f2d1f..655ef52e7 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -2669,7 +2669,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -2720,7 +2726,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -2746,7 +2758,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index ef0c47ac2..203bf20b0 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/no-shadow */ @@ -18,9 +19,13 @@ import { get } from 'lodash'; import { BINARY_ENCODING, NodeExecuteFunctions } from 'n8n-core'; import { + createDeferredPromise, IBinaryKeyData, IDataObject, + IDeferredPromise, IExecuteData, + IExecuteResponsePromiseData, + IN8nHttpFullResponse, INode, IRunExecutionData, IWebhookData, @@ -34,20 +39,20 @@ import { } from 'n8n-workflow'; // eslint-disable-next-line import/no-cycle import { - ActiveExecutions, GenericHelpers, IExecutionDb, IResponseCallbackData, IWorkflowDb, IWorkflowExecutionDataProcess, ResponseHelper, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowExecuteAdditionalData, WorkflowHelpers, WorkflowRunner, } from '.'; +// eslint-disable-next-line import/no-cycle +import * as ActiveExecutions from './ActiveExecutions'; + const activeExecutions = ActiveExecutions.getInstance(); /** @@ -91,6 +96,35 @@ export function getWorkflowWebhooks( return returnData; } +export function decodeWebhookResponse( + response: IExecuteResponsePromiseData, +): IExecuteResponsePromiseData { + if ( + typeof response === 'object' && + typeof response.body === 'object' && + (response.body as IDataObject)['__@N8nEncodedBuffer@__'] + ) { + response.body = Buffer.from( + (response.body as IDataObject)['__@N8nEncodedBuffer@__'] as string, + BINARY_ENCODING, + ); + } + + return response; +} + +export function encodeWebhookResponse( + response: IExecuteResponsePromiseData, +): IExecuteResponsePromiseData { + if (typeof response === 'object' && Buffer.isBuffer(response.body)) { + response.body = { + '__@N8nEncodedBuffer@__': response.body.toString(BINARY_ENCODING), + }; + } + + return response; +} + /** * Returns all the webhooks which should be created for the give workflow * @@ -169,7 +203,7 @@ export async function executeWebhook( 200, ) as number; - if (!['onReceived', 'lastNode'].includes(responseMode as string)) { + if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode as string)) { // If the mode is not known we error. Is probably best like that instead of using // the default that people know as early as possible (probably already testing phase) // that something does not resolve properly. @@ -356,9 +390,52 @@ export async function executeWebhook( workflowData, }; + let responsePromise: IDeferredPromise | undefined; + if (responseMode === 'responseNode') { + responsePromise = await createDeferredPromise(); + responsePromise + .promise() + .then((response: IN8nHttpFullResponse) => { + if (didSendResponse) { + return; + } + + if (Buffer.isBuffer(response.body)) { + res.header(response.headers); + res.end(response.body); + + responseCallback(null, { + noWebhookResponse: true, + }); + } else { + // TODO: This probably needs some more changes depending on the options on the + // Webhook Response node + responseCallback(null, { + data: response.body as IDataObject, + headers: response.headers, + responseCode: response.statusCode, + }); + } + + didSendResponse = true; + }) + .catch(async (error) => { + Logger.error( + `Error with Webhook-Response for execution "${executionId}": "${error.message}"`, + { executionId, workflowId: workflow.id }, + ); + }); + } + // Start now to run the workflow const workflowRunner = new WorkflowRunner(); - executionId = await workflowRunner.run(runData, true, !didSendResponse, executionId); + executionId = await workflowRunner.run( + runData, + true, + !didSendResponse, + executionId, + responsePromise, + ); Logger.verbose( `Started execution of workflow "${workflow.name}" from webhook with execution ID ${executionId}`, @@ -398,6 +475,20 @@ export async function executeWebhook( return data; } + if (responseMode === 'responseNode') { + if (!didSendResponse) { + // Return an error if no Webhook-Response node did send any data + responseCallback(null, { + data: { + message: 'Workflow executed sucessfully.', + }, + responseCode, + }); + didSendResponse = true; + } + return undefined; + } + if (returnData === undefined) { if (!didSendResponse) { responseCallback(null, { diff --git a/packages/cli/src/WebhookServer.ts b/packages/cli/src/WebhookServer.ts index 4cf3afc7b..c63526bfc 100644 --- a/packages/cli/src/WebhookServer.ts +++ b/packages/cli/src/WebhookServer.ts @@ -64,7 +64,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -115,7 +121,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -141,7 +153,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -173,7 +191,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -199,7 +223,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -225,7 +255,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); } diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 8984384aa..fd18ff3d0 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -15,6 +15,8 @@ import { IProcessMessage, WorkflowExecute } from 'n8n-core'; import { ExecutionError, + IDeferredPromise, + IExecuteResponsePromiseData, IRun, LoggerProxy as Logger, Workflow, @@ -41,9 +43,7 @@ import { IBullJobResponse, ICredentialsOverwrite, ICredentialsTypeData, - IExecutionDb, IExecutionFlattedDb, - IExecutionResponse, IProcessMessageDataHook, ITransferNodeTypes, IWorkflowExecutionDataProcess, @@ -51,6 +51,7 @@ import { NodeTypes, Push, ResponseHelper, + WebhookHelpers, WorkflowExecuteAdditionalData, WorkflowHelpers, } from '.'; @@ -146,6 +147,7 @@ export class WorkflowRunner { loadStaticData?: boolean, realtime?: boolean, executionId?: string, + responsePromise?: IDeferredPromise, ): Promise { const executionsProcess = config.get('executions.process') as string; const executionsMode = config.get('executions.mode') as string; @@ -153,11 +155,17 @@ export class WorkflowRunner { if (executionsMode === 'queue' && data.executionMode !== 'manual') { // Do not run "manual" executions in bull because sending events to the // frontend would not be possible - executionId = await this.runBull(data, loadStaticData, realtime, executionId); + executionId = await this.runBull( + data, + loadStaticData, + realtime, + executionId, + responsePromise, + ); } else if (executionsProcess === 'main') { - executionId = await this.runMainProcess(data, loadStaticData, executionId); + executionId = await this.runMainProcess(data, loadStaticData, executionId, responsePromise); } else { - executionId = await this.runSubprocess(data, loadStaticData, executionId); + executionId = await this.runSubprocess(data, loadStaticData, executionId, responsePromise); } const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId); @@ -200,6 +208,7 @@ export class WorkflowRunner { data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { if (loadStaticData === true && data.workflowData.id) { data.workflowData.staticData = await WorkflowHelpers.getStaticDataById( @@ -256,6 +265,15 @@ export class WorkflowRunner { executionId, true, ); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + if (responsePromise) { + responsePromise.resolve(response); + } + }, + ]; + additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId, }); @@ -341,11 +359,15 @@ export class WorkflowRunner { loadStaticData?: boolean, realtime?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { // TODO: If "loadStaticData" is set to true it has to load data new on worker // Register the active execution const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId); + if (responsePromise) { + this.activeExecutions.attachResponsePromise(executionId, responsePromise); + } const jobData: IBullJobData = { executionId, @@ -545,6 +567,7 @@ export class WorkflowRunner { data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { let startedAt = new Date(); const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js')); @@ -653,6 +676,10 @@ export class WorkflowRunner { } else if (message.type === 'end') { clearTimeout(executionTimeout); this.activeExecutions.remove(executionId, message.data.runData); + } else if (message.type === 'sendResponse') { + if (responsePromise) { + responsePromise.resolve(WebhookHelpers.decodeWebhookResponse(message.data.response)); + } } else if (message.type === 'sendMessageToUI') { // eslint-disable-next-line @typescript-eslint/no-unsafe-call WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })( diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index d7039d69a..e8b8274c9 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -10,6 +10,7 @@ import { IProcessMessage, UserSettings, WorkflowExecute } from 'n8n-core'; import { ExecutionError, IDataObject, + IExecuteResponsePromiseData, IExecuteWorkflowInfo, ILogger, INodeExecutionData, @@ -33,6 +34,7 @@ import { IWorkflowExecuteProcess, IWorkflowExecutionDataProcessWithExecution, NodeTypes, + WebhookHelpers, WorkflowExecuteAdditionalData, WorkflowHelpers, } from '.'; @@ -200,6 +202,15 @@ export class WorkflowRunnerProcess { workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000, ); additionalData.hooks = this.getProcessForwardHooks(); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + await sendToParentProcess('sendResponse', { + response: WebhookHelpers.encodeWebhookResponse(response), + }); + }, + ]; + additionalData.executionId = inputData.executionId; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index d6dc5da8a..29b0c140e 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -22,6 +22,7 @@ import { ICredentialsExpressionResolveValues, IDataObject, IExecuteFunctions, + IExecuteResponsePromiseData, IExecuteSingleFunctions, IExecuteWorkflowInfo, IHttpRequestOptions, @@ -1635,6 +1636,9 @@ export function getExecuteFunctions( Logger.warn(`There was a problem sending messsage to UI: ${error.message}`); } }, + async sendResponse(response: IExecuteResponsePromiseData): Promise { + await additionalData.hooks?.executeHookFunctions('sendResponse', [response]); + }, helpers: { httpRequest, prepareBinaryData, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 15d1cce2b..b0c6167aa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -12,7 +12,6 @@ export * from './ActiveWorkflows'; export * from './ActiveWebhooks'; export * from './Constants'; export * from './Credentials'; -export * from './DeferredPromise'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; export * from './NodeExecuteFunctions'; diff --git a/packages/core/test/Helpers.ts b/packages/core/test/Helpers.ts index 387ac67a8..eb5920182 100644 --- a/packages/core/test/Helpers.ts +++ b/packages/core/test/Helpers.ts @@ -4,6 +4,7 @@ import { ICredentialDataDecryptedObject, ICredentialsHelper, IDataObject, + IDeferredPromise, IExecuteWorkflowInfo, INodeCredentialsDetails, INodeExecutionData, @@ -20,7 +21,7 @@ import { WorkflowHooks, } from 'n8n-workflow'; -import { Credentials, IDeferredPromise, IExecuteFunctions } from '../src'; +import { Credentials, IExecuteFunctions } from '../src'; export class CredentialsHelper extends ICredentialsHelper { getDecrypted( diff --git a/packages/core/test/WorkflowExecute.test.ts b/packages/core/test/WorkflowExecute.test.ts index 364fb23d5..b1ac658dc 100644 --- a/packages/core/test/WorkflowExecute.test.ts +++ b/packages/core/test/WorkflowExecute.test.ts @@ -1,6 +1,14 @@ -import { IConnections, ILogger, INode, IRun, LoggerProxy, Workflow } from 'n8n-workflow'; +import { + createDeferredPromise, + IConnections, + ILogger, + INode, + IRun, + LoggerProxy, + Workflow, +} from 'n8n-workflow'; -import { createDeferredPromise, WorkflowExecute } from '../src'; +import { WorkflowExecute } from '../src'; import * as Helpers from './Helpers'; diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook.node.ts new file mode 100644 index 000000000..352516815 --- /dev/null +++ b/packages/nodes-base/nodes/RespondToWebhook.node.ts @@ -0,0 +1,278 @@ +import { + BINARY_ENCODING, +} from 'n8n-core'; + +import { + IDataObject, + IExecuteFunctions, + IN8nHttpFullResponse, + IN8nHttpResponse, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; + +export class RespondToWebhook implements INodeType { + description: INodeTypeDescription = { + displayName: 'Respond to Webhook', + icon: 'file:webhook.svg', + name: 'respondToWebhook', + group: ['transform'], + version: 1, + description: 'Returns data for Webhook', + defaults: { + name: 'Respond to Webhook', + color: '#885577', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + ], + properties: [ + { + displayName: 'Respond With', + name: 'respondWith', + type: 'options', + options: [ + { + name: 'First Incoming Item', + value: 'firstIncomingItem', + }, + { + name: 'Text', + value: 'text', + }, + { + name: 'JSON', + value: 'json', + }, + { + name: 'Binary', + value: 'binary', + }, + { + name: 'No Data', + value: 'noData', + }, + ], + default: 'firstIncomingItem', + description: 'The data that should be returned', + }, + { + displayName: 'When using expressions, note that this node will only run for the first item in the input data.', + name: 'webhookNotice', + type: 'notice', + displayOptions: { + show: { + respondWith: [ + 'json', + 'text', + ], + }, + }, + default: '', + }, + { + displayName: 'Response Body', + name: 'responseBody', + type: 'json', + displayOptions: { + show: { + respondWith: [ + 'json', + ], + }, + }, + default: '', + placeholder: '{ "key": "value" }', + description: 'The HTTP Response JSON data', + }, + { + displayName: 'Response Body', + name: 'responseBody', + type: 'string', + displayOptions: { + show: { + respondWith: [ + 'text', + ], + }, + }, + default: '', + placeholder: 'e.g. Workflow started', + description: 'The HTTP Response text data', + }, + { + displayName: 'Response Data Source', + name: 'responseDataSource', + type: 'options', + displayOptions: { + show: { + respondWith: [ + 'binary', + ], + }, + }, + options: [ + { + name: 'Choose Automatically From Input', + value: 'automatically', + description: 'Use if input data will contain a single piece of binary data', + }, + { + name: 'Specify Myself', + value: 'set', + description: 'Enter the name of the input field the binary data will be in', + }, + ], + default: 'automatically', + }, + { + displayName: 'Input Field Name', + name: 'inputFieldName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + respondWith: [ + 'binary', + ], + responseDataSource: [ + 'set', + ], + }, + }, + description: 'The name of the node input field with the binary data', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Response Code', + name: 'responseCode', + type: 'number', + typeOptions: { + minValue: 100, + maxValue: 599, + }, + default: 200, + description: 'The HTTP Response code to return. Defaults to 200.', + }, + { + displayName: 'Response Headers', + name: 'responseHeaders', + placeholder: 'Add Response Header', + description: 'Add headers to the webhook response', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'entries', + displayName: 'Entries', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the header', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the header', + }, + ], + }, + ], + }, + ], + }, + ], + }; + + execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const respondWith = this.getNodeParameter('respondWith', 0) as string; + const options = this.getNodeParameter('options', 0, {}) as IDataObject; + + const headers = {} as IDataObject; + if (options.responseHeaders) { + for (const header of (options.responseHeaders as IDataObject).entries as IDataObject[]) { + if (typeof header.name !== 'string') { + header.name = header.name?.toString(); + } + headers[header.name?.toLowerCase() as string] = header.value?.toString(); + } + } + + let responseBody: IN8nHttpResponse; + if (respondWith === 'json') { + const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string; + if (responseBodyParameter) { + responseBody = JSON.parse(responseBodyParameter); + } + } else if (respondWith === 'firstIncomingItem') { + responseBody = items[0].json; + } else if (respondWith === 'text') { + responseBody = this.getNodeParameter('responseBody', 0) as string; + } else if (respondWith === 'binary') { + const item = this.getInputData()[0]; + + if (item.binary === undefined) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); + } + + let responseBinaryPropertyName: string; + + const responseDataSource = this.getNodeParameter('responseDataSource', 0) as string; + + if (responseDataSource === 'set') { + responseBinaryPropertyName = this.getNodeParameter('inputFieldName', 0) as string; + } else { + const binaryKeys = Object.keys(item.binary); + if (binaryKeys.length === 0) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); + } + responseBinaryPropertyName = binaryKeys[0]; + } + + const binaryData = item.binary[responseBinaryPropertyName]; + + if (binaryData === undefined) { + throw new NodeOperationError(this.getNode(), `No binary data property "${responseBinaryPropertyName}" does not exists on item!`); + } + + if (headers['content-type']) { + headers['content-type'] = binaryData.mimeType; + } + responseBody = Buffer.from(binaryData.data, BINARY_ENCODING); + } else if (respondWith !== 'noData') { + throw new NodeOperationError(this.getNode(), `The Response Data option "${respondWith}" is not supported!`); + } + + const response: IN8nHttpFullResponse = { + body: responseBody, + headers, + statusCode: options.responseCode as number || 200, + }; + + this.sendResponse(response); + + return this.prepareOutputData(items); + } + +} diff --git a/packages/nodes-base/nodes/Wait.node.ts b/packages/nodes-base/nodes/Wait.node.ts index 2a5f064a6..e8c3609b1 100644 --- a/packages/nodes-base/nodes/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait.node.ts @@ -304,6 +304,11 @@ export class Wait implements INodeType { value: 'lastNode', description: 'Returns data of the last executed node', }, + { + name: 'Response Node finishes', + value: 'responseNode', + description: 'Returns data the response node did set', + }, ], default: 'onReceived', description: 'When and how to respond to the webhook', diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook.node.ts index e04fe2813..1e53c1529 100644 --- a/packages/nodes-base/nodes/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook.node.ts @@ -9,7 +9,6 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, - NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -143,10 +142,54 @@ export class Webhook implements INodeType { required: true, description: 'The path to listen to.', }, + { + displayName: 'Respond', + name: 'responseMode', + type: 'options', + options: [ + { + name: 'Immediately', + value: 'onReceived', + description: 'As soon as this node executes', + }, + { + name: 'When last node finishes', + value: 'lastNode', + description: 'Returns data of the last-executed node', + }, + { + name: 'Using \'Respond to Webhook\' node', + value: 'responseNode', + description: 'Response defined in that node', + }, + ], + default: 'onReceived', + description: 'When and how to respond to the webhook.', + }, + { + displayName: 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details', + name: 'webhookNotice', + type: 'notice', + displayOptions: { + show: { + responseMode: [ + 'responseNode', + ], + }, + }, + default: '', + }, { displayName: 'Response Code', name: 'responseCode', type: 'number', + displayOptions: { + hide: { + responseMode: [ + 'responseNode', + ], + }, + }, typeOptions: { minValue: 100, maxValue: 599, @@ -154,25 +197,6 @@ export class Webhook implements INodeType { default: 200, description: 'The HTTP Response code to return', }, - { - displayName: 'Respond When', - name: 'responseMode', - type: 'options', - options: [ - { - name: 'Webhook received', - value: 'onReceived', - description: 'Returns directly with defined Response Code', - }, - { - name: 'Last node finishes', - value: 'lastNode', - description: 'Returns data of the last executed node', - }, - ], - default: 'onReceived', - description: 'When and how to respond to the webhook.', - }, { displayName: 'Response Data', name: 'responseData', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 969fc9971..9d107f7f1 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -552,6 +552,7 @@ "dist/nodes/Reddit/Reddit.node.js", "dist/nodes/Redis/Redis.node.js", "dist/nodes/RenameKeys.node.js", + "dist/nodes/RespondToWebhook.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/RssFeedRead.node.js", "dist/nodes/Rundeck/Rundeck.node.js", diff --git a/packages/core/src/DeferredPromise.ts b/packages/workflow/src/DeferredPromise.ts similarity index 100% rename from packages/core/src/DeferredPromise.ts rename to packages/workflow/src/DeferredPromise.ts diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index b700d069f..7d7dfb6da 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -6,6 +6,7 @@ import * as express from 'express'; import * as FormData from 'form-data'; import { URLSearchParams } from 'url'; +import { IDeferredPromise } from './DeferredPromise'; import { Workflow } from './Workflow'; import { WorkflowHooks } from './WorkflowHooks'; import { WorkflowOperationError } from './WorkflowErrors'; @@ -208,6 +209,9 @@ export interface IDataObject { [key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[]; } +// export type IExecuteResponsePromiseData = IDataObject; +export type IExecuteResponsePromiseData = IDataObject | IN8nHttpFullResponse; + export interface INodeTypeNameVersion { name: string; version: number; @@ -324,13 +328,13 @@ export interface IHttpRequestOptions { json?: boolean; } -export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[]; +export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[] | null; export interface IN8nHttpFullResponse { body: IN8nHttpResponse; headers: IDataObject; statusCode: number; - statusMessage: string; + statusMessage?: string; } export interface IExecuteFunctions { @@ -371,7 +375,8 @@ export interface IExecuteFunctions { outputIndex?: number, ): Promise; putExecutionToWait(waitTill: Date): Promise; - sendMessageToUI(message: any): void; + sendMessageToUI(message: any): void; // tslint:disable-line:no-any + sendResponse(response: IExecuteResponsePromiseData): void; // tslint:disable-line:no-any helpers: { httpRequest( requestOptions: IHttpRequestOptions, @@ -492,7 +497,10 @@ export interface IPollFunctions { } export interface ITriggerFunctions { - emit(data: INodeExecutionData[][]): void; + emit( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ): void; getCredentials(type: string): Promise; getMode(): WorkflowExecuteMode; getActivationMode(): WorkflowActivateMode; @@ -975,6 +983,7 @@ export interface IWorkflowExecuteHooks { nodeExecuteBefore?: Array<(nodeName: string) => Promise>; workflowExecuteAfter?: Array<(data: IRun, newStaticData: IDataObject) => Promise>; workflowExecuteBefore?: Array<(workflow: Workflow, data: IRunExecutionData) => Promise>; + sendResponse?: Array<(response: IExecuteResponsePromiseData) => Promise>; } export interface IWorkflowExecuteAdditionalData { diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 860eaea0d..256b5aa2b 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -16,6 +16,8 @@ import { Expression, IConnections, + IDeferredPromise, + IExecuteResponsePromiseData, IGetExecuteTriggerFunctions, INode, INodeExecuteFunctions, @@ -946,10 +948,23 @@ export class Workflow { // Add the manual trigger response which resolves when the first time data got emitted triggerResponse!.manualTriggerResponse = new Promise((resolve) => { - // eslint-disable-next-line @typescript-eslint/no-shadow - triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => { - resolve(data); - })(resolve); + triggerFunctions.emit = ( + (resolveEmit) => + ( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ) => { + additionalData.hooks!.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + if (responsePromise) { + responsePromise.resolve(response); + } + }, + ]; + + resolveEmit(data); + } + )(resolve); }); return triggerResponse; diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index e73f572cd..9913f7a3e 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -3,6 +3,7 @@ import * as LoggerProxy from './LoggerProxy'; import * as NodeHelpers from './NodeHelpers'; import * as ObservableObject from './ObservableObject'; +export * from './DeferredPromise'; export * from './Interfaces'; export * from './Expression'; export * from './NodeErrors'; From 8c4040dc5b6cb3f3d1ba0303f082fdbfc3cf2c25 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 11:19:23 -0600 Subject: [PATCH 41/86] :zap: Minor improvements to RespondToWebhook node --- packages/cli/src/NodeTypes.ts | 3 +++ packages/nodes-base/nodes/Wait.node.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/NodeTypes.ts b/packages/cli/src/NodeTypes.ts index b6ed97511..ff4e8c027 100644 --- a/packages/cli/src/NodeTypes.ts +++ b/packages/cli/src/NodeTypes.ts @@ -40,6 +40,9 @@ class NodeTypesClass implements INodeTypes { } getByNameAndVersion(nodeType: string, version?: number): INodeType { + if (this.nodeTypes[nodeType] === undefined) { + throw new Error(`The node-type "${nodeType}" is not known!`); + } return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version); } } diff --git a/packages/nodes-base/nodes/Wait.node.ts b/packages/nodes-base/nodes/Wait.node.ts index e8c3609b1..78279c541 100644 --- a/packages/nodes-base/nodes/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait.node.ts @@ -283,7 +283,7 @@ export class Wait implements INodeType { description: 'The HTTP Response code to return', }, { - displayName: 'Respond When', + displayName: 'Respond', name: 'responseMode', type: 'options', displayOptions: { @@ -295,19 +295,19 @@ export class Wait implements INodeType { }, options: [ { - name: 'Webhook received', + name: 'Immediately', value: 'onReceived', - description: 'Returns directly with defined Response Code', + description: 'As soon as this node executes', }, { - name: 'Last node finishes', + name: 'When last node finishes', value: 'lastNode', - description: 'Returns data of the last executed node', + description: 'Returns data of the last-executed node', }, { - name: 'Response Node finishes', + name: 'Using \'Respond to Webhook\' node', value: 'responseNode', - description: 'Returns data the response node did set', + description: 'Response defined in that node', }, ], default: 'onReceived', From 18597808f31d6c47af607d37964198bd66c3f5f7 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 5 Nov 2021 13:37:50 -0400 Subject: [PATCH 42/86] :sparkles: Add Dropcontact node (#2394) * Add a new dropcontact node * Improvements to #2389 * :zap: Add credentials verification * :zap: Small improvement * :zap: set default time to 45 seconds * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :bug: Set siren and language correctly Co-authored-by: PaulineDropcontact Co-authored-by: Jan Oberhauser --- .../credentials/DropcontactApi.credentials.ts | 18 + .../nodes/Dropcontact/Dropcontact.node.ts | 370 ++++++++++++++++++ .../nodes/Dropcontact/GenericFunction.ts | 82 ++++ .../nodes/Dropcontact/dropcontact.svg | 3 + packages/nodes-base/package.json | 2 + 5 files changed, 475 insertions(+) create mode 100644 packages/nodes-base/credentials/DropcontactApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/GenericFunction.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/dropcontact.svg diff --git a/packages/nodes-base/credentials/DropcontactApi.credentials.ts b/packages/nodes-base/credentials/DropcontactApi.credentials.ts new file mode 100644 index 000000000..de91630b0 --- /dev/null +++ b/packages/nodes-base/credentials/DropcontactApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class DropcontactApi implements ICredentialType { + name = 'dropcontactApi'; + displayName = 'Dropcontact API'; + documentationUrl = 'dropcontact'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts new file mode 100644 index 000000000..6269903dd --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts @@ -0,0 +1,370 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeApiError, + NodeCredentialTestResult, +} from 'n8n-workflow'; + +import { + dropcontactApiRequest, + validateCrendetials, +} from './GenericFunction'; + +export class Dropcontact implements INodeType { + description: INodeTypeDescription = { + displayName: 'Dropcontact', + name: 'dropcontact', + icon: 'file:dropcontact.svg', + group: ['transform'], + version: 1, + description: 'Find B2B emails and enrich contacts', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'Dropcontact', + color: '#0ABA9F', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'dropcontactApi', + required: true, + testedBy: 'dropcontactApiCredentialTest', + }, + ], + properties: [ + { + displayName: 'Resource', + noDataExpression: true, + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + ], + default: 'contact', + required: true, + }, + { + displayName: 'Operation', + noDataExpression: true, + name: 'operation', + type: 'options', + options: [ + { + name: 'Enrich', + value: 'enrich', + description: 'Find B2B emails and enrich your contact from his name and his website', + }, + { + name: 'Fetch Request', + value: 'fetchRequest', + }, + ], + default: 'enrich', + required: true, + }, + { + displayName: 'Request ID', + name: 'requestId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'fetchRequest', + ], + }, + }, + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + default: '', + }, + { + displayName: 'Simplify Output (Faster)', + name: 'simplify', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + default: false, + description: 'When off, waits for the contact data before completing. Waiting time can be adjusted with Extend Wait Time option. When on, returns a request_id that can be used later in the Fetch Request operation.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + options: [ + { + displayName: 'Company SIREN Number', + name: 'num_siren', + type: 'string', + default: '', + }, + { + displayName: 'Company SIRET Code', + name: 'siret', + type: 'string', + default: '', + }, + { + displayName: 'Company Name', + name: 'company', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'first_name', + type: 'string', + default: '', + }, + { + displayName: 'Full Name', + name: 'full_name', + type: 'string', + default: '', + }, + { + displayName: 'Last Name', + name: 'last_name', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn Profile', + name: 'linkedin', + type: 'string', + default: '', + }, + { + displayName: 'Phone Number', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Data Fetch Wait Time', + name: 'waitTime', + type: 'number', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + '/simplify': [ + false, + ], + }, + }, + default: 45, + description: 'When not simplifying the response, data will be fetched in two steps. This parameter controls how long to wait (in seconds) before trying the second step', + }, + { + displayName: 'French Company Enrich', + name: 'siren', + type: 'boolean', + default: false, + description: `Whether you want the SIREN number, NAF code, TVA number, company address and informations about the company leader.
+ Only applies to french companies`, + }, + { + displayName: 'Language', + name: 'language', + type: 'options', + options: [ + { + name: 'English', + value: 'en', + }, + { + name: 'French', + value: 'fr', + }, + ], + default: 'en', + description: 'Whether the response is in English or French', + }, + ], + }, + ], + }; + + methods = { + credentialTest: { + async dropcontactApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + try { + await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject); + } catch (error) { + return { + status: 'Error', + message: 'The API Key included in the request is invalid', + }; + } + + return { + status: 'OK', + message: 'Connection successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const entryData = this.getInputData(); + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + // tslint:disable-next-line: no-any + let responseData: any; + const returnData: IDataObject[] = []; + + if (resource === 'contact') { + if (operation === 'enrich') { + const options = this.getNodeParameter('options', 0) as IDataObject; + const data = []; + const simplify = this.getNodeParameter('simplify', 0) as boolean; + + const siren = options.siren === true ? true : false; + const language = options.language ? options.language : 'en'; + + for (let i = 0; i < entryData.length; i++) { + const email = this.getNodeParameter('email', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i); + const body: IDataObject = {}; + if (email !== '') { + body.email = email; + } + Object.assign(body, additionalFields); + data.push(body); + } + + responseData = await dropcontactApiRequest.call(this, 'POST', '/batch', { data, siren, language }, {}) as { request_id: string, error: string, success: boolean }; + + if (!responseData.success) { + if (this.continueOnFail()) { + returnData.push({ error: responseData.reason || 'invalid request' }); + } else { + throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' }); + } + } + + if (simplify === false) { + const waitTime = this.getNodeParameter('options.waitTime', 0, 45) as number; + // tslint:disable-next-line: no-any + const delay = (ms: any) => new Promise(res => setTimeout(res, ms * 1000)); + await delay(waitTime); + responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${responseData.request_id}`, {}, {}); + if (!responseData.success) { + if (this.continueOnFail()) { + responseData.push({ error: responseData.reason }); + } else { + throw new NodeApiError(this.getNode(), { + error: responseData.reason, + description: 'Hint: Increase the Wait Time to avoid this error', + }); + } + } else { + returnData.push(...responseData.data); + } + } else { + returnData.push(responseData); + } + } + + if (operation === 'fetchRequest') { + for (let i = 0; i < entryData.length; i++) { + const requestId = this.getNodeParameter('requestId', i) as string; + responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${requestId}`, {}, {}) as { request_id: string, error: string, success: boolean }; + if (!responseData.success) { + if (this.continueOnFail()) { + responseData.push({ error: responseData.reason || 'invalid request' }); + } else { + throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' }); + } + } + returnData.push(...responseData.data); + } + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts b/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts new file mode 100644 index 000000000..e5af1e620 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts @@ -0,0 +1,82 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialTestFunctions, + IDataObject, + ILoadOptionsFunctions, + NodeApiError, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +/** + * Make an authenticated API request to Bubble. + */ +export async function dropcontactApiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject, + qs: IDataObject, +) { + + const { apiKey } = await this.getCredentials('dropcontactApi') as { + apiKey: string, + }; + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + 'X-Access-Token': apiKey, + }, + method, + uri: `https://api.dropcontact.io${endpoint}`, + qs, + body, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function validateCrendetials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise { // tslint:disable-line:no-any + const credentials = decryptedCredentials; + + const { apiKey } = credentials as { + apiKey: string, + }; + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + 'X-Access-Token': apiKey, + }, + method: 'POST', + body: { + data: [{ email: '' }], + }, + uri: `https://api.dropcontact.io/batch`, + json: true, + }; + + return this.helpers.request!(options); +} + diff --git a/packages/nodes-base/nodes/Dropcontact/dropcontact.svg b/packages/nodes-base/nodes/Dropcontact/dropcontact.svg new file mode 100644 index 000000000..447973cc5 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/dropcontact.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 9d107f7f1..5edffc0b5 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -78,6 +78,7 @@ "dist/credentials/DriftOAuth2Api.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/DropboxOAuth2Api.credentials.js", + "dist/credentials/DropcontactApi.credentials.js", "dist/credentials/EgoiApi.credentials.js", "dist/credentials/ElasticsearchApi.credentials.js", "dist/credentials/ElasticSecurityApi.credentials.js", @@ -379,6 +380,7 @@ "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Drift/Drift.node.js", "dist/nodes/Dropbox/Dropbox.node.js", + "dist/nodes/Dropcontact/Dropcontact.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/Egoi/Egoi.node.js", "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js", From 2e8f09dcfd79796668b4eaef9246a978a3500f53 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:06 +0000 Subject: [PATCH 43/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@0.?= =?UTF-8?q?75.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 a86acf16d..3027a0201 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.74.0", + "version": "0.75.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From ace08020170ebc17e5528b279543ccfaeb40b24c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:16 +0000 Subject: [PATCH 44/86] :arrow_up: Set n8n-workflow@0.75.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 853d39847..74e372f80 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.74.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From dc06ee60fb60b6cd2c8587d805289635d6531ac1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:16 +0000 Subject: [PATCH 45/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.92.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 74e372f80..9ac21f0de 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.91.0", + "version": "0.92.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 9e218152780cc89408c15e3b9f8299bfe6b5e5f1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:23 +0000 Subject: [PATCH 46/86] :arrow_up: Set n8n-core@0.92.0 and n8n-workflow@0.75.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 4d45c2831..a834ef27b 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.91.0", - "n8n-workflow": "~0.74.0", + "n8n-core": "~0.92.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From c16b20bd3da8f970c0af4c0c6c5cc9368f7b7129 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:23 +0000 Subject: [PATCH 47/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?32.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 a834ef27b..baed211bb 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.31.0", + "version": "0.32.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d427f942bdcd71dd9bd244eea2f7d4479552909c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:32 +0000 Subject: [PATCH 48/86] :arrow_up: Set n8n-core@0.92.0 and n8n-workflow@0.75.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 5edffc0b5..7d0995201 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -671,7 +671,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.74.0", + "n8n-workflow": "~0.75.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -711,7 +711,7 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.3.0", - "n8n-core": "~0.91.0", + "n8n-core": "~0.92.0", "node-ssh": "^12.0.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", From 298c88e326a3c84949d8f05dc6614c5cdeb104c5 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:32 +0000 Subject: [PATCH 49/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.145.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 7d0995201..3034a6ab2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.144.1", + "version": "0.145.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 55455524e437592d154fa847ce5f7bef5bd883ce Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:10 +0000 Subject: [PATCH 50/86] :arrow_up: Set n8n-workflow@0.75.0 on n8n-editor-ui --- 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 60e7485c8..c93fe1296 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -71,7 +71,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.74.0", + "n8n-workflow": "~0.75.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From 9ac41953ab7e13b512442e99eb139f3d9a2512bc Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:10 +0000 Subject: [PATCH 51/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.115.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 c93fe1296..35a98903e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.114.0", + "version": "0.115.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7f8dbfa4056b55606ef563232974f3255dd9384d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:48 +0000 Subject: [PATCH 52/86] :arrow_up: Set n8n-core@0.92.0, n8n-editor-ui@0.115.0, n8n-nodes-base@0.145.0 and n8n-workflow@0.75.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 f4924adbe..ec78029cb 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.91.0", - "n8n-editor-ui": "~0.114.0", - "n8n-nodes-base": "~0.144.1", - "n8n-workflow": "~0.74.0", + "n8n-core": "~0.92.0", + "n8n-editor-ui": "~0.115.0", + "n8n-nodes-base": "~0.145.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From 27543fcdd4064d6527e3c8e31c2e4927a85acf26 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:48 +0000 Subject: [PATCH 53/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.148.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 ec78029cb..29cee52fd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.147.1", + "version": "0.148.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 34fea51f1e7826405f5a0f258c2cfa198a258fed Mon Sep 17 00:00:00 2001 From: GeylaniBerk Date: Thu, 4 Nov 2021 10:58:31 +0100 Subject: [PATCH 54/86] :bug: Adding credential test for Zendesk API Token --- .../nodes-base/nodes/Zendesk/Zendesk.node.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index 22718413d..b6d9d173d 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -1,8 +1,14 @@ +import { + OptionsWithUri, +} from 'request'; + import { IExecuteFunctions, } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, ILoadOptionsFunctions, INodeExecutionData, @@ -10,6 +16,7 @@ import { INodeType, INodeTypeDescription, NodeApiError, + NodeCredentialTestResult, NodeOperationError, } from 'n8n-workflow'; @@ -70,6 +77,7 @@ export class Zendesk implements INodeType { ], }, }, + testedBy: 'zendeskSoftwareApiTest', }, { name: 'zendeskOAuth2Api', @@ -146,6 +154,42 @@ export class Zendesk implements INodeType { }; methods = { + credentialTest: { + async zendeskSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + const subdomain = credentials!.subdomain; + const email = credentials!.email; + const apiToken = credentials!.apiToken; + + const base64Key = Buffer.from(`${email}/token:${apiToken}`).toString('base64'); + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${base64Key}`, + }, + method: 'GET', + uri: `https://${subdomain}.zendesk.com/api/v2/ticket_fields.json`, + qs: { + recent: 0, + }, + json: true, + timeout: 5000, + }; + + try { + const response = await this.helpers.request!(options); + } catch (error) { + return { + status: 'Error', + message: `Connection details not valid; ${error.message}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; + }, + }, loadOptions: { // Get all the custom fields to display them to user so that he can // select them easily From 653a8bb42ea150e3305970c26a35af399a460fcf Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 9 Nov 2021 22:04:45 +0100 Subject: [PATCH 55/86] :bug: Fix bug with internal hooks and CLI workflow execution --- packages/cli/commands/execute.ts | 4 ++++ packages/cli/commands/executeBatch.ts | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index b6641628d..b74eb7397 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -11,6 +11,7 @@ import { CredentialTypes, Db, ExternalHooks, + InternalHooksManager, IWorkflowBase, IWorkflowExecutionDataProcess, LoadNodesAndCredentials, @@ -123,6 +124,9 @@ export class Execute extends Command { const externalHooks = ExternalHooks(); await externalHooks.init(); + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); diff --git a/packages/cli/commands/executeBatch.ts b/packages/cli/commands/executeBatch.ts index d4489c38d..4834e69d6 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -28,6 +28,7 @@ import { CredentialTypes, Db, ExternalHooks, + InternalHooksManager, IWorkflowDb, IWorkflowExecutionDataProcess, LoadNodesAndCredentials, @@ -55,12 +56,12 @@ export class ExecuteBatch extends Command { static executionTimeout = 3 * 60 * 1000; static examples = [ - `$ n8n executeAll`, - `$ n8n executeAll --concurrency=10 --skipList=/data/skipList.txt`, - `$ n8n executeAll --debug --output=/data/output.json`, - `$ n8n executeAll --ids=10,13,15 --shortOutput`, - `$ n8n executeAll --snapshot=/data/snapshots --shallow`, - `$ n8n executeAll --compare=/data/previousExecutionData --retries=2`, + `$ n8n executeBatch`, + `$ n8n executeBatch --concurrency=10 --skipList=/data/skipList.txt`, + `$ n8n executeBatch --debug --output=/data/output.json`, + `$ n8n executeBatch --ids=10,13,15 --shortOutput`, + `$ n8n executeBatch --snapshot=/data/snapshots --shallow`, + `$ n8n executeBatch --compare=/data/previousExecutionData --retries=2`, ]; static flags = { @@ -303,6 +304,9 @@ export class ExecuteBatch extends Command { const externalHooks = ExternalHooks(); await externalHooks.init(); + const instanceId = await UserSettings.getInstanceId(); + InternalHooksManager.init(instanceId); + // Add the found types to an instance other parts of the application can use const nodeTypes = NodeTypes(); await nodeTypes.init(loadNodesAndCredentials.nodeTypes); From e8133d80f8998ba638494939fcf1e2aa8f707c27 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Nov 2021 08:49:45 +0100 Subject: [PATCH 56/86] :bug: Improve expression security --- packages/workflow/src/Expression.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 6a92d2e26..2b65ef03b 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -99,6 +99,19 @@ export class Expression { ); const data = dataProxy.getDataProxy(); + // Support only a subset of process properties + // @ts-ignore + data.process = { + arch: process.arch, + env: process.env, + platform: process.platform, + pid: process.pid, + ppid: process.ppid, + release: process.release, + version: process.pid, + versions: process.versions, + }; + // Execute the expression try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call From 3c6f38d045cb06096b3e75d417d7c28614658494 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 10 Nov 2021 16:48:20 -0500 Subject: [PATCH 57/86] :sparkles: Add OneSimpleAPI Node (#2360) * Start of OneSimpleAPI Node * Node functionality is complete * :zap: Improvements to #2357 * :zap: Add internal feedback * :zap: Minor improvements Co-authored-by: Jonathan Co-authored-by: Jan Oberhauser --- .../credentials/OneSimpleApi.credentials.ts | 19 + .../nodes/OneSimpleApi/GenericFunctions.ts | 41 + .../nodes/OneSimpleApi/OneSimpleApi.node.json | 20 + .../nodes/OneSimpleApi/OneSimpleApi.node.ts | 867 ++++++++++++++++++ .../nodes/OneSimpleApi/onesimpleapi.svg | 25 + packages/nodes-base/package.json | 2 + 6 files changed, 974 insertions(+) create mode 100644 packages/nodes-base/credentials/OneSimpleApi.credentials.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json create mode 100644 packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts create mode 100644 packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg diff --git a/packages/nodes-base/credentials/OneSimpleApi.credentials.ts b/packages/nodes-base/credentials/OneSimpleApi.credentials.ts new file mode 100644 index 000000000..63ec3d13a --- /dev/null +++ b/packages/nodes-base/credentials/OneSimpleApi.credentials.ts @@ -0,0 +1,19 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + + +export class OneSimpleApi implements ICredentialType { + name = 'oneSimpleApi'; + displayName = 'One Simple API'; + documentationUrl = 'oneSimpleApi'; + properties: INodeProperties[] = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string', + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts b/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts new file mode 100644 index 000000000..5e89c0315 --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/GenericFunctions.ts @@ -0,0 +1,41 @@ +import { + OptionsWithUri +} from 'request'; + +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +export async function oneSimpleApiRequest(this: IExecuteFunctions, method: string, resource: string, body: IDataObject = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}) { + const credentials = await this.getCredentials('oneSimpleApi'); + if (credentials === undefined) { + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); + } + + const outputFormat = 'json'; + let options: OptionsWithUri = { + method, + body, + qs, + uri: uri || `https://onesimpleapi.com/api${resource}?token=${credentials.apiToken}&output=${outputFormat}`, + json: true, + }; + options = Object.assign({}, options, option); + + if (Object.keys(body).length === 0) { + delete options.body; + } + + try { + const responseData = await this.helpers.request(options); + return responseData; + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json new file mode 100644 index 000000000..bd85e8ee3 --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.oneSimpleApi", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Utility" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/OneSimpleAPI" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.oneSimpleApi/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts new file mode 100644 index 000000000..5edda2ee4 --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.ts @@ -0,0 +1,867 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + oneSimpleApiRequest, +} from './GenericFunctions'; + +export class OneSimpleApi implements INodeType { + description: INodeTypeDescription = { + displayName: 'One Simple API', + name: 'oneSimpleApi', + icon: 'file:onesimpleapi.svg', + group: ['transform'], + version: 1, + description: 'A toolbox of no-code utilities', + defaults: { + name: 'One Simple API', + color: '#1A82e2', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'oneSimpleApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Information', + value: 'information', + }, + { + name: 'Utility', + value: 'utility', + }, + { + name: 'Website', + value: 'website', + }, + ], + default: 'website', + required: true, + }, + // Generation + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'website', + ], + }, + }, + options: [ + { + name: 'Generate PDF', + value: 'pdf', + description: 'Generate a PDF from a webpage', + }, + { + name: 'Get SEO Data', + value: 'seo', + description: 'Get SEO information from website', + }, + { + name: 'Take Screenshot', + value: 'screenshot', + description: 'Create a screenshot from a webpage', + }, + ], + default: 'pdf', + }, + // Information + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'information', + ], + }, + }, + options: [ + { + name: 'Exchange Rate', + value: 'exchangeRate', + description: 'Convert a value between currencies', + }, + { + name: 'Image Metadata', + value: 'imageMetadata', + description: 'Retrieve image metadata from a URL', + }, + ], + default: 'exchangeRate', + description: 'The operation to perform.', + }, + // Utiliy + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'utility', + ], + }, + }, + options: [ + { + name: 'Expand URL', + value: 'expandURL', + description: 'Expand a shortened url', + }, + { + name: 'Generate QR Code', + value: 'qrCode', + description: 'Generate a QR Code', + }, + { + name: 'Validate Email', + value: 'validateEmail', + description: 'Validate an email address', + }, + ], + default: 'validateEmail', + description: 'The operation to perform.', + }, + // website: pdf + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Link to webpage to convert', + }, + { + displayName: 'Download PDF?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + }, + }, + default: false, + description: 'Whether to download the PDF or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'pdf', + ], + resource: [ + 'website', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'pdf', + ], + }, + }, + options: [ + { + displayName: 'Page Size', + name: 'page', + type: 'options', + options: [ + { + name: 'A0', + value: 'A0', + }, + { + name: 'A1', + value: 'A1', + }, + { + name: 'A2', + value: 'A2', + }, + { + name: 'A3', + value: 'A3', + }, + { + name: 'A4', + value: 'A4', + }, + { + name: 'A5', + value: 'A5', + }, + { + name: 'A6', + value: 'A6', + }, + { + name: 'Legal', + value: 'Legal', + }, + { + name: 'Ledger', + value: 'Ledger', + }, + { + name: 'Letter', + value: 'Letter', + }, + { + name: 'Tabloid', + value: 'Tabloid', + }, + ], + default: '', + description: 'The page size', + }, + { + displayName: 'Force Refresh', + name: 'force', + type: 'boolean', + default: false, + description: `Normally the API will reuse a previously taken screenshot of the URL to give a faster response. + This option allows you to retake the screenshot at that exact time, for those times when it's necessary`, + }, + ], + }, + // website: qrCode + { + displayName: 'QR Content', + name: 'message', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'The text that should be turned into a QR code - like a website URL', + }, + { + displayName: 'Download Image?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + }, + }, + default: false, + description: 'Whether to download the QR code or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'qrCode', + ], + resource: [ + 'utility', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'utility', + ], + operation: [ + 'qrCode', + ], + }, + }, + options: [ + { + displayName: 'Size', + name: 'size', + type: 'options', + options: [ + { + name: 'Small', + value: 'Small', + }, + { + name: 'Medium', + value: 'Medium', + }, + { + name: 'Large', + value: 'Large', + }, + ], + default: 'Small', + description: 'The QR Code size', + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + options: [ + { + name: 'PNG', + value: 'PNG', + }, + { + name: 'SVG', + value: 'SVG', + }, + ], + default: 'PNG', + description: 'The QR Code format', + }, + ], + }, + // website: screenshot + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Link to webpage to convert', + }, + { + displayName: 'Download Screenshot?', + name: 'download', + type: 'boolean', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + }, + }, + default: false, + description: 'Whether to download the screenshot or return a link to it', + }, + { + displayName: 'Put Output In Field', + name: 'output', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'screenshot', + ], + resource: [ + 'website', + ], + download: [ + true, + ], + }, + }, + default: 'data', + description: 'The name of the output field to put the binary file data in', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'screenshot', + ], + }, + }, + options: [ + { + displayName: 'Screen Size', + name: 'screen', + type: 'options', + options: [ + { + name: 'Phone', + value: 'phone', + }, + { + name: 'Phone Landscape', + value: 'phone-landscape', + }, + { + name: 'Retina', + value: 'retina', + }, + { + name: 'Tablet', + value: 'tablet', + }, + { + name: 'Tablet Landscape', + value: 'tablet-landscape', + }, + ], + default: '', + description: 'The screen size', + }, + { + displayName: 'Force Refresh', + name: 'force', + type: 'boolean', + default: false, + description: `Normally the API will reuse a previously taken screenshot of the URL to give a faster response. + This option allows you to retake the screenshot at that exact time, for those times when it's necessary`, + }, + { + displayName: 'Full Page', + name: 'fullpage', + type: 'boolean', + default: false, + description: 'The API takes a screenshot of the viewable area for the desired screen size. If you need a screenshot of the whole length of the page, use this option', + }, + ], + }, + // information: exchangeRate + { + displayName: 'Value', + name: 'value', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'Value to convert', + }, + { + displayName: 'From Currency', + name: 'fromCurrency', + type: 'string', + required: true, + placeholder: 'USD', + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'From Currency', + }, + { + displayName: 'To Currency', + name: 'toCurrency', + type: 'string', + placeholder: 'EUR', + required: true, + displayOptions: { + show: { + operation: [ + 'exchangeRate', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'To Currency', + }, + // information: imageMetadata + { + displayName: 'Link To Image', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'imageMetadata', + ], + resource: [ + 'information', + ], + }, + }, + default: '', + description: 'Image to get metadata from', + }, + // website: seo + { + displayName: 'Webpage URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'seo', + ], + resource: [ + 'website', + ], + }, + }, + default: '', + description: 'Webpage to get SEO information for', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'website', + ], + operation: [ + 'seo', + ], + }, + }, + options: [ + { + displayName: 'Include Headers?', + name: 'headers', + type: 'boolean', + default: false, + description: '', + }, + ], + }, + // utility: validateEmail + { + displayName: 'Email Address', + name: 'emailAddress', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'validateEmail', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'Email Address', + }, + // utility: expandURL + { + displayName: 'URL', + name: 'link', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'expandURL', + ], + resource: [ + 'utility', + ], + }, + }, + default: '', + description: 'URL to unshorten', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + let download; + for (let i = 0; i < length; i++) { + try { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + if (resource === 'website') { + if (operation === 'pdf') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + qs.url = link; + + if (options.page) { + qs.page = options.page as string; + } + + if (options.force) { + qs.force = 'yes'; + } else { + qs.force = 'no'; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/pdf', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + + if (operation === 'screenshot') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + + qs.url = link; + + if (options.screen) { + qs.screen = options.screen as string; + } + + if (options.fullpage) { + qs.fullpage = 'yes'; + } else { + qs.fullpage = 'no'; + } + + if (options.force) { + qs.force = 'yes'; + } else { + qs.force = 'no'; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/screenshot', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + + if (operation === 'seo') { + const link = this.getNodeParameter('link', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + qs.url = link; + + if (options.headers) { + qs.headers = 'yes'; + } + + responseData = await oneSimpleApiRequest.call(this, 'GET', '/page_info', {}, qs); + } + } + + if (resource === 'information') { + if (operation === 'exchangeRate') { + const value = this.getNodeParameter('value', i) as string; + const fromCurrency = this.getNodeParameter('fromCurrency', i) as string; + const toCurrency = this.getNodeParameter('toCurrency', i) as string; + qs.from_currency = fromCurrency; + qs.to_currency = toCurrency; + qs.from_value = value; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/exchange_rate', {}, qs); + } + + if (operation === 'imageMetadata') { + const link = this.getNodeParameter('link', i) as string; + qs.url = link; + qs.raw = true; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/image_info', {}, qs); + } + } + + if (resource === 'utility') { + // validateEmail + if (operation === 'validateEmail') { + const emailAddress = this.getNodeParameter('emailAddress', i) as string; + qs.email = emailAddress; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/email', {}, qs); + } + // expandURL + if (operation === 'expandURL') { + const url = this.getNodeParameter('link', i) as string; + qs.url = url; + responseData = await oneSimpleApiRequest.call(this, 'GET', '/unshorten', {}, qs); + } + + if (operation === 'qrCode') { + const message = this.getNodeParameter('message', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + download = this.getNodeParameter('download', i) as boolean; + + qs.message = message; + + if (options.size) { + qs.size = options.size as string; + } + + if (options.format) { + qs.format = options.format as string; + } + + const response = await oneSimpleApiRequest.call(this, 'GET', '/qr_code', {}, qs); + + if (download) { + const output = this.getNodeParameter('output', i) as string; + const buffer = await oneSimpleApiRequest.call(this, 'GET', '', {}, {}, response.url, { json: false, encoding: null }) as Buffer; + responseData = { + json: response, + binary: { + [output]: await this.helpers.prepareBinaryData(buffer), + }, + }; + } else { + responseData = response; + } + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + + if (download) { + return this.prepareOutputData(returnData as unknown as INodeExecutionData[]); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg b/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg new file mode 100644 index 000000000..9a918f0a9 --- /dev/null +++ b/packages/nodes-base/nodes/OneSimpleApi/onesimpleapi.svg @@ -0,0 +1,25 @@ + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3034a6ab2..c51772500 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -201,6 +201,7 @@ "dist/credentials/NotionOAuth2Api.credentials.js", "dist/credentials/OAuth1Api.credentials.js", "dist/credentials/OAuth2Api.credentials.js", + "dist/credentials/OneSimpleApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/OrbitApi.credentials.js", "dist/credentials/OuraApi.credentials.js", @@ -520,6 +521,7 @@ "dist/nodes/Notion/NotionTrigger.node.js", "dist/nodes/N8nTrainingCustomerDatastore.node.js", "dist/nodes/N8nTrainingCustomerMessenger.node.js", + "dist/nodes/OneSimpleApi/OneSimpleApi.node.js", "dist/nodes/OpenThesaurus/OpenThesaurus.node.js", "dist/nodes/OpenWeatherMap.node.js", "dist/nodes/Orbit/Orbit.node.js", From 1a1bc26ecf3cfddaa99533d22adad06896be12dc Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 10 Nov 2021 18:03:45 -0500 Subject: [PATCH 58/86] :zap: Add role parameter to user:update (Zulip) (#2336) * :zap: Add role parameter to user:update * :pencil2: Fix typo issue --- .../nodes-base/nodes/Zulip/UserDescription.ts | 33 +++++++++++++++++-- .../nodes-base/nodes/Zulip/UserInterface.ts | 1 + packages/nodes-base/nodes/Zulip/Zulip.node.ts | 3 ++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Zulip/UserDescription.ts b/packages/nodes-base/nodes/Zulip/UserDescription.ts index 1890f71c9..164432e5b 100644 --- a/packages/nodes-base/nodes/Zulip/UserDescription.ts +++ b/packages/nodes-base/nodes/Zulip/UserDescription.ts @@ -226,14 +226,14 @@ export const userFields = [ name: 'isAdmin', type: 'boolean', default: false, - description: 'Whether the target user is an administrator.', + description: 'Whether the target user is an administrator', }, { displayName: 'Is Guest', name: 'isGuest', type: 'boolean', default: false, - description: 'Whether the target user is a guest.', + description: 'Whether the target user is a guest', }, { displayName: 'Profile Data', @@ -268,6 +268,35 @@ export const userFields = [ }, ], }, + { + displayName: 'Role', + name: 'role', + type: 'options', + options: [ + { + name: 'Organization Owner', + value: 100, + }, + { + name: 'Organization Administrator', + value: 200, + }, + { + name: 'Organization Moderator', + value: 300, + }, + { + name: 'Member', + value: 400, + }, + { + name: 'Guest', + value: 600, + }, + ], + default: '', + description: 'Role for the user', + }, ], }, diff --git a/packages/nodes-base/nodes/Zulip/UserInterface.ts b/packages/nodes-base/nodes/Zulip/UserInterface.ts index fd7dffcea..12ff6d63b 100644 --- a/packages/nodes-base/nodes/Zulip/UserInterface.ts +++ b/packages/nodes-base/nodes/Zulip/UserInterface.ts @@ -8,4 +8,5 @@ export interface IUser { email?: string; password?: string; short_name?: string; + role?: number; } diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts index 9281e90dc..1e9e9c9bd 100644 --- a/packages/nodes-base/nodes/Zulip/Zulip.node.ts +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -431,6 +431,9 @@ export class Zulip implements INodeType { if (additionalFields.isGuest) { body.is_guest = additionalFields.isGuest as boolean; } + if (additionalFields.role) { + body.role = additionalFields.role as number; + } if (additionalFields.profileData) { //@ts-ignore body.profile_data = additionalFields.profileData.properties as [{}]; From dc2bda4baa9d45fc9edce2862531a17f5bff9179 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 11 Nov 2021 12:08:05 +0100 Subject: [PATCH 59/86] :zap: Minor improvements --- packages/nodes-base/nodes/Jira/Jira.node.ts | 2 +- packages/nodes-base/nodes/Zendesk/Zendesk.node.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 3cd050956..579840d3a 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -179,7 +179,7 @@ export class Jira implements INodeType { } catch (err) { return { status: 'Error', - message: `Connection details not valid; ${err.message}`, + message: `Connection details not valid: ${err.message}`, }; } return { diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index b6d9d173d..83c7850e7 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -177,11 +177,11 @@ export class Zendesk implements INodeType { }; try { - const response = await this.helpers.request!(options); + await this.helpers.request!(options); } catch (error) { return { status: 'Error', - message: `Connection details not valid; ${error.message}`, + message: `Connection details not valid: ${error.message}`, }; } return { From abdcb0836e07944b4585b7052c9f1cbf299c8537 Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 12 Nov 2021 13:53:47 +0100 Subject: [PATCH 60/86] :zap: Add codex files (#2431) --- .../nodes/Dropcontact/Dropcontact.node.json | 20 +++++++++++++++++++ .../nodes/RespondToWebhook.node.json | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json create mode 100644 packages/nodes-base/nodes/RespondToWebhook.node.json diff --git a/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json new file mode 100644 index 000000000..4b47e8e54 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.dropcontact", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Sales" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/dropcontact" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.dropcontact/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.json b/packages/nodes-base/nodes/RespondToWebhook.node.json new file mode 100644 index 000000000..99bf635ad --- /dev/null +++ b/packages/nodes-base/nodes/RespondToWebhook.node.json @@ -0,0 +1,19 @@ +{ + "node": "n8n-nodes-base.respondToWebhook", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Core Nodes", + "Utility" + ], + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.respondToWebhook/" + } + ] + }, + "subcategories": { + "Core Nodes":["Flow"] + } +} From 15e64d1bc44802aed2a63a6557f439229e9c5631 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Fri, 12 Nov 2021 13:55:29 +0100 Subject: [PATCH 61/86] :bug: Add function to calculate content-length when using multipart/form-data (#2427) --- packages/core/src/NodeExecuteFunctions.ts | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 29b0c140e..7fcf72440 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -135,6 +135,28 @@ function searchForHeader(headers: IDataObject, headerName: string) { return headerNames.find((thisHeader) => thisHeader.toLowerCase() === headerName); } +async function generateContentLengthHeader(formData: FormData, headers: IDataObject) { + if (!formData || !formData.getLength) { + return; + } + try { + const length = await new Promise((res, rej) => { + formData.getLength((error: Error | null, length: number) => { + if (error) { + rej(error); + return; + } + res(length); + }); + }); + headers = Object.assign(headers, { + 'content-length': length, + }); + } catch (error) { + Logger.error('Unable to calculate form data length', { error }); + } +} + async function parseRequestObject(requestObject: IDataObject) { // This function is a temporary implementation // That translates all http requests done via @@ -199,6 +221,7 @@ async function parseRequestObject(requestObject: IDataObject) { delete axiosConfig.headers[contentTypeHeaderKeyName]; const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); + await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); } else { // When using the `form` property it means the content should be x-www-form-urlencoded. if (requestObject.form !== undefined && requestObject.body === undefined) { @@ -235,6 +258,7 @@ async function parseRequestObject(requestObject: IDataObject) { // Mix in headers as FormData creates the boundary. const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); + await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); } else if (requestObject.body !== undefined) { // If we have body and possibly form if (requestObject.form !== undefined) { From 357178d83b0ac6f8714f3950951bf52f0a9e5294 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Fri, 12 Nov 2021 14:28:49 +0100 Subject: [PATCH 62/86] :zap: New JSON attributes are now considered warnings in testing workflows (#2432) --- packages/cli/commands/executeBatch.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/cli/commands/executeBatch.ts b/packages/cli/commands/executeBatch.ts index 4834e69d6..587f91c2a 100644 --- a/packages/cli/commands/executeBatch.ts +++ b/packages/cli/commands/executeBatch.ts @@ -817,10 +817,22 @@ export class ExecuteBatch extends Command { const changes = diff(JSON.parse(contents), data, { keysOnly: true }); if (changes !== undefined) { - // we have structural changes. Report them. - executionResult.error = `Workflow may contain breaking changes`; - executionResult.changes = changes; - executionResult.executionStatus = 'error'; + // If we had only additions with no removals + // Then we treat as a warning and not an error. + // To find this, we convert the object to JSON + // and search for the `__deleted` string + const changesJson = JSON.stringify(changes); + if (changesJson.includes('__deleted')) { + // we have structural changes. Report them. + executionResult.error = 'Workflow may contain breaking changes'; + executionResult.changes = changes; + executionResult.executionStatus = 'error'; + } else { + executionResult.error = + 'Workflow contains new data that previously did not exist.'; + executionResult.changes = changes; + executionResult.executionStatus = 'warning'; + } } else { executionResult.executionStatus = 'success'; } From 670e93c0f439abb739431b4c50a26d7dcaca602c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 09:37:42 +0100 Subject: [PATCH 63/86] :shirt: Fix lint issue --- packages/cli/src/Server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 655ef52e7..9cf50c40b 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1580,11 +1580,11 @@ class App { const findQuery = {} as FindManyOptions; if (req.query.filter) { findQuery.where = JSON.parse(req.query.filter as string); - if ((findQuery.where! as IDataObject).id !== undefined) { + if (findQuery.where.id !== undefined) { // No idea if multiple where parameters make db search // slower but to be sure that that is not the case we // remove all unnecessary fields in case the id is defined. - findQuery.where = { id: (findQuery.where! as IDataObject).id }; + findQuery.where = { id: findQuery.where.id }; } } From 6a1ca823122cf3071b6a01fc585b1af60f93fca1 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 13 Nov 2021 09:39:22 +0100 Subject: [PATCH 64/86] :sparkles: Edit-Image addition (circle + composite operator) (#2419) * Add a new dropcontact node * Improvements to #2389 * :zap: Add credentials verification * :zap: Small improvement * :zap: set default time to 45 seconds * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :bug: Set siren and language correctly * :sparkles: Add support to draw circle and composite operator * :zap: Improve naming Co-authored-by: PaulineDropcontact Co-authored-by: ricardo --- packages/nodes-base/nodes/EditImage.node.ts | 119 +++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage.node.ts index a499cabeb..ec34be54f 100644 --- a/packages/nodes-base/nodes/EditImage.node.ts +++ b/packages/nodes-base/nodes/EditImage.node.ts @@ -152,6 +152,10 @@ const nodeOperationOptions: INodeProperties[] = [ }, }, options: [ + { + name: 'Circle', + value: 'circle', + }, { name: 'Line', value: 'line', @@ -192,6 +196,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -210,6 +215,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -228,6 +234,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -246,6 +253,7 @@ const nodeOperationOptions: INodeProperties[] = [ 'draw', ], primitive: [ + 'circle', 'line', 'rectangle', ], @@ -472,6 +480,110 @@ const nodeOperationOptions: INodeProperties[] = [ }, description: 'The name of the binary property which contains the data of the image to composite on top of image which is found in Property Name.', }, + { + displayName: 'Operator', + name: 'operator', + type: 'options', + displayOptions: { + show: { + operation: [ + 'composite', + ], + }, + }, + options: [ + { + name: 'Add', + value: 'Add', + }, + { + name: 'Atop', + value: 'Atop', + }, + { + name: 'Bumpmap', + value: 'Bumpmap', + }, + { + name: 'Copy', + value: 'Copy', + }, + { + name: 'Copy Black', + value: 'CopyBlack', + }, + { + name: 'Copy Blue', + value: 'CopyBlue', + }, + { + name: 'Copy Cyan', + value: 'CopyCyan', + }, + { + name: 'Copy Green', + value: 'CopyGreen', + }, + { + name: 'Copy Magenta', + value: 'CopyMagenta', + }, + { + name: 'Copy Opacity', + value: 'CopyOpacity', + }, + { + name: 'Copy Red', + value: 'CopyRed', + }, + { + name: 'Copy Yellow', + value: 'CopyYellow', + }, + { + name: 'Difference', + value: 'Difference', + }, + { + name: 'Divide', + value: 'Divide', + }, + { + name: 'In', + value: 'In', + }, + { + name: 'Minus', + value: 'Minus', + }, + { + name: 'Multiply', + value: 'Multiply', + }, + { + name: 'Out', + value: 'Out', + }, + { + name: 'Over', + value: 'Over', + }, + { + name: 'Plus', + value: 'Plus', + }, + { + name: 'Subtract', + value: 'Subtract', + }, + { + name: 'Xor', + value: 'Xor', + }, + ], + default: 'Over', + description: 'The operator to use to combine the images.', + }, { displayName: 'Position X', name: 'positionX', @@ -1095,6 +1207,7 @@ export class EditImage implements INodeType { } else if (operationData.operation === 'composite') { const positionX = operationData.positionX as number; const positionY = operationData.positionY as number; + const operator = operationData.operator as string; const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY; @@ -1109,9 +1222,9 @@ export class EditImage implements INodeType { if (operations[0].operation === 'create') { // It seems like if the image gets created newly we have to create a new gm instance // else it fails for some reason - gmInstance = gm(gmInstance!.stream('png')).geometry(geometryString).composite(path); + gmInstance = gm(gmInstance!.stream('png')).compose(operator).geometry(geometryString).composite(path); } else { - gmInstance = gmInstance!.geometry(geometryString).composite(path); + gmInstance = gmInstance!.compose(operator).geometry(geometryString).composite(path); } if (operations.length !== i + 1) { @@ -1131,6 +1244,8 @@ export class EditImage implements INodeType { if (operationData.primitive === 'line') { gmInstance = gmInstance.drawLine(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number); + } else if (operationData.primitive === 'circle') { + gmInstance = gmInstance.drawCircle(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number); } else if (operationData.primitive === 'rectangle') { gmInstance = gmInstance.drawRectangle(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number, operationData.cornerRadius as number || undefined); } From 7a0e072d98811ed7cfd0e700f986ecd96c303178 Mon Sep 17 00:00:00 2001 From: Tom McAtee Date: Sat, 13 Nov 2021 19:44:25 +1030 Subject: [PATCH 65/86] :bug: Fix Toggl Trigger Node (#2418) Updating API URL as per https://support.toggl.com/en/articles/5708431-why-did-my-integration-stop-working #2417 --- packages/nodes-base/nodes/Toggl/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts index c694cf600..b8386abb9 100644 --- a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts @@ -25,7 +25,7 @@ export async function togglApiRequest(this: ITriggerFunctions | IPollFunctions | headers: headerWithAuthentication, method, qs: query, - uri: uri || `https://www.toggl.com/api/v8${resource}`, + uri: uri || `https://api.track.toggl.com/api/v8${resource}`, body, json: true, }; From 345c94bd37616212114e24f887bf11a8908197d1 Mon Sep 17 00:00:00 2001 From: Tom <19203795+that-one-tom@users.noreply.github.com> Date: Sat, 13 Nov 2021 10:16:56 +0100 Subject: [PATCH 66/86] :bug: Google Tasks: Fix due field (#2426) --- packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts | 4 ++-- packages/nodes-base/nodes/Google/Task/TaskDescription.ts | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts index f95625191..37b02c934 100644 --- a/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts +++ b/packages/nodes-base/nodes/Google/Task/GoogleTasks.node.ts @@ -124,7 +124,7 @@ export class GoogleTasks implements INodeType { body.notes = additionalFields.notes as string; } if (additionalFields.dueDate) { - body.dueDate = additionalFields.dueDate as string; + body.due = additionalFields.dueDate as string; } if (additionalFields.completed) { @@ -249,7 +249,7 @@ export class GoogleTasks implements INodeType { } if (updateFields.dueDate) { - body.dueDate = updateFields.dueDate as string; + body.due = updateFields.dueDate as string; } if (updateFields.completed) { diff --git a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts index aaf2892ab..90747569d 100644 --- a/packages/nodes-base/nodes/Google/Task/TaskDescription.ts +++ b/packages/nodes-base/nodes/Google/Task/TaskDescription.ts @@ -447,6 +447,13 @@ export const taskFields = [ default: false, description: 'Flag indicating whether the task has been deleted.', }, + { + displayName: 'Due Date', + name: 'dueDate', + type: 'dateTime', + default: '', + description: 'Due date of the task.', + }, { displayName: 'Notes', name: 'notes', From 8427ade2e6886edfb2dc9de24fb2205dfbdaa00a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 10:54:14 +0100 Subject: [PATCH 67/86] :bug: Allow Stripe Webhook creation if old does not exist anymore #2429 --- packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts index 58829d9ae..12ac34816 100644 --- a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts +++ b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts @@ -820,7 +820,7 @@ export class StripeTrigger implements INodeType { try { await stripeApiRequest.call(this, 'GET', endpoint, {}); } catch (error) { - if (error.message.includes('resource_missing')) { + if (error.httpCode === '404' || error.message.includes('resource_missing')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; From c6fec12bec46245da9ac7736c1bb15767106f3e3 Mon Sep 17 00:00:00 2001 From: Max Mayr Date: Sat, 13 Nov 2021 11:06:16 +0100 Subject: [PATCH 68/86] :bug: Fix permission issue with custom images (#2355) --- docker/images/n8n-custom/docker-entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/images/n8n-custom/docker-entrypoint.sh b/docker/images/n8n-custom/docker-entrypoint.sh index 2dd4dae10..acd6a6019 100755 --- a/docker/images/n8n-custom/docker-entrypoint.sh +++ b/docker/images/n8n-custom/docker-entrypoint.sh @@ -6,6 +6,8 @@ if [ -d /root/.n8n ] ; then ln -s /root/.n8n /home/node/ fi +chown -R node /home/node + if [ "$#" -gt 0 ]; then # Got started with arguments COMMAND=$1; From 1db7d178b879ce767fe9380d3e0b375df54319bb Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:03 +0000 Subject: [PATCH 69/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@0.?= =?UTF-8?q?76.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 3027a0201..2e35eff0d 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.75.0", + "version": "0.76.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From b8e83e0eea32bc05f1b3c6cf9bf4320fd975cfb4 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:11 +0000 Subject: [PATCH 70/86] :arrow_up: Set n8n-workflow@0.76.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 9ac21f0de..8cdc3b2a8 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.75.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From 86c234f55761af766d49adbb30a3b117577d56ee Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:11 +0000 Subject: [PATCH 71/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.93.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 8cdc3b2a8..dfc9c18a5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.92.0", + "version": "0.93.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From f9ba3fa1d547146c71669aaaa46535a1ffab4862 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:19 +0000 Subject: [PATCH 72/86] :arrow_up: Set n8n-core@0.93.0 and n8n-workflow@0.76.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 baed211bb..ec7dd4ab9 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.92.0", - "n8n-workflow": "~0.75.0", + "n8n-core": "~0.93.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From ecb265b72faeaa22d8ea5ceac9966f3e04e3c729 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:19 +0000 Subject: [PATCH 73/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?33.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 ec7dd4ab9..3c2de68f4 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.32.0", + "version": "0.33.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 96f178003cb3151c8adaee98add65bd7e6345ed6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:28 +0000 Subject: [PATCH 74/86] :arrow_up: Set n8n-core@0.93.0 and n8n-workflow@0.76.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 c51772500..d4817e9ba 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -673,7 +673,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.75.0", + "n8n-workflow": "~0.76.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -713,7 +713,7 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.3.0", - "n8n-core": "~0.92.0", + "n8n-core": "~0.93.0", "node-ssh": "^12.0.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", From f3c27cf506314162580a3093ae2fc1e48153ab87 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:11:29 +0000 Subject: [PATCH 75/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.146.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 d4817e9ba..b17229cd9 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.145.0", + "version": "0.146.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e887aeea957a15ef95bb717b8e6aca86c679500e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:06 +0000 Subject: [PATCH 76/86] :arrow_up: Set n8n-workflow@0.76.0 on n8n-editor-ui --- 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 35a98903e..ea8d0ea2f 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -71,7 +71,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.75.0", + "n8n-workflow": "~0.76.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From bfaa2634bc7a86348a7221e434434e2bc7acd2b1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:06 +0000 Subject: [PATCH 77/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.116.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 ea8d0ea2f..93be8548e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.115.0", + "version": "0.116.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 3ecd78dd29bc0c86aec10cabac4381ad77245248 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:39 +0000 Subject: [PATCH 78/86] :arrow_up: Set n8n-core@0.93.0, n8n-editor-ui@0.116.0, n8n-nodes-base@0.146.0 and n8n-workflow@0.76.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 29cee52fd..2b0c4eace 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.92.0", - "n8n-editor-ui": "~0.115.0", - "n8n-nodes-base": "~0.145.0", - "n8n-workflow": "~0.75.0", + "n8n-core": "~0.93.0", + "n8n-editor-ui": "~0.116.0", + "n8n-nodes-base": "~0.146.0", + "n8n-workflow": "~0.76.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From dec81a171a7b90eecbe84eab01cb75f578c6c754 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 13 Nov 2021 12:12:39 +0000 Subject: [PATCH 79/86] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.149.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 2b0c4eace..070ab83f9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.148.0", + "version": "0.149.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7a37f73eaed32e88123dfcc48847f42b1cfcf193 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 14 Nov 2021 00:11:50 +0100 Subject: [PATCH 80/86] :bug: Improve expression security --- packages/workflow/src/Expression.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 2b65ef03b..b7fd4d013 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -112,6 +112,9 @@ export class Expression { versions: process.versions, }; + // @ts-ignore + data.document = {}; + // Execute the expression try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call From 9f7113c94b2ccda78ea12611bbfadd226d4178df Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 15 Nov 2021 17:20:28 +0100 Subject: [PATCH 81/86] :bug: Remove default headers for PUT and PATCH (#2434) --- packages/core/src/NodeExecuteFunctions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 7fcf72440..fa1894a1d 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -87,6 +87,8 @@ import { axios.defaults.timeout = 300000; // Prevent axios from adding x-form-www-urlencoded headers by default axios.defaults.headers.post = {}; +axios.defaults.headers.put = {}; +axios.defaults.headers.patch = {}; axios.defaults.paramsSerializer = (params) => { if (params instanceof URLSearchParams) { return params.toString(); From 0022c7eb099374eb0b9346bfe8539a05d7324507 Mon Sep 17 00:00:00 2001 From: Oliver Trajceski Date: Mon, 15 Nov 2021 17:31:00 +0100 Subject: [PATCH 82/86] :bug: Fix issue that Start-Node did not get reset (#2425) * N8N-2549 Editor UI - Disabled start node when reseting new workflow * N8N-2549 Editor UI - Disabled start node when reseting new workflow, reseting the position of the default node * N8N-2549 Updated Editor-ui - Resetting Default Node (disable, position and all props) when reseting new workflow * N8N-2549 Remove comment --- packages/editor-ui/src/views/NodeView.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 3e6abff17..b8b5ad7d3 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1607,7 +1607,9 @@ export default mixins( await this.$store.dispatch('workflows/setNewWorkflowName'); this.$store.commit('setStateDirty', false); - await this.addNodes([DEFAULT_START_NODE]); + const nodes = [{...DEFAULT_START_NODE}]; + + await this.addNodes(nodes); this.$store.commit('setStateDirty', false); this.setZoomLevel(1); From 766f74c7825c35a7624cc34da8f5dccdb57e8382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 17 Nov 2021 17:30:14 +0100 Subject: [PATCH 83/86] :truck: Directorize and alphabetize nodes (#2445) * :truck: Directorize nodes * :zap: Alphabetize nodes and credentials * :fire: Remove unused node * :fire: Remove unused codex * :fire: Remove duplicate cred file references * :bug: Fix node file paths * :fire: Remove duplicate node reference --- .../nodes/ActivationTrigger.node.json | 15 - .../{ => Compression}/Compression.node.json | 0 .../{ => Compression}/Compression.node.ts | 0 .../nodes/{ => Cron}/Cron.node.json | 0 .../nodes-base/nodes/{ => Cron}/Cron.node.ts | 0 .../nodes/{ => Crypto}/Crypto.node.json | 0 .../nodes/{ => Crypto}/Crypto.node.ts | 0 .../nodes/{ => DateTime}/DateTime.node.json | 0 .../nodes/{ => DateTime}/DateTime.node.ts | 0 .../nodes/{ => EditImage}/EditImage.node.json | 0 .../nodes/{ => EditImage}/EditImage.node.ts | 0 .../EmailReadImap.node.json | 0 .../{ => EmailReadImap}/EmailReadImap.node.ts | 0 .../nodes/{ => EmailSend}/EmailSend.node.json | 0 .../nodes/{ => EmailSend}/EmailSend.node.ts | 0 .../{ => ErrorTrigger}/ErrorTrigger.node.json | 0 .../{ => ErrorTrigger}/ErrorTrigger.node.ts | 0 .../ExecuteCommand.node.json | 0 .../ExecuteCommand.node.ts | 0 .../ExecuteWorkflow.node.json | 0 .../ExecuteWorkflow.node.ts | 0 .../nodes-base/nodes/{ => Ftp}/Ftp.node.json | 0 .../nodes-base/nodes/{ => Ftp}/Ftp.node.ts | 0 .../nodes/{ => Function}/Function.node.json | 0 .../nodes/{ => Function}/Function.node.ts | 0 .../{ => FunctionItem}/FunctionItem.node.json | 0 .../{ => FunctionItem}/FunctionItem.node.ts | 0 .../{ => HttpRequest}/HttpRequest.node.json | 0 .../{ => HttpRequest}/HttpRequest.node.ts | 0 .../nodes/{ => ICalendar}/ICalendar.node.json | 0 .../nodes/{ => ICalendar}/ICalendar.node.ts | 0 .../nodes-base/nodes/{ => If}/If.node.json | 0 packages/nodes-base/nodes/{ => If}/If.node.ts | 0 .../nodes/{ => Interval}/Interval.node.json | 0 .../nodes/{ => Interval}/Interval.node.ts | 0 .../nodes/{ => ItemLists}/ItemLists.node.json | 0 .../nodes/{ => ItemLists}/ItemLists.node.ts | 0 .../nodes/{ => ItemLists}/itemLists.svg | 0 .../LocalFileTrigger.node.json | 0 .../LocalFileTrigger.node.ts | 0 .../nodes/{ => Merge}/Merge.node.json | 0 .../nodes/{ => Merge}/Merge.node.ts | 0 .../MoveBinaryData.node.json | 0 .../MoveBinaryData.node.ts | 0 .../N8nTrainingCustomerDatastore.node.ts | 0 .../N8nTrainingCustomerDatastore.svg} | 0 .../N8nTrainingCustomerMessenger.node.ts | 0 .../N8nTrainingCustomerMessenger.svg} | 0 .../{ => N8nTrigger}/N8nTrigger.node.json | 0 .../nodes/{ => N8nTrigger}/N8nTrigger.node.ts | 0 .../nodes/{ => N8nTrigger}/n8nTrigger.svg | 0 .../nodes/{ => NoOp}/NoOp.node.json | 0 .../nodes-base/nodes/{ => NoOp}/NoOp.node.ts | 0 .../OpenWeatherMap.node.json | 0 .../OpenWeatherMap.node.ts | 0 .../ReadBinaryFile.node.json | 0 .../ReadBinaryFile.node.ts | 0 .../ReadBinaryFiles.node.json | 0 .../ReadBinaryFiles.node.ts | 0 .../nodes/{ => ReadPdf}/ReadPdf.node.json | 0 .../nodes/{ => ReadPdf}/ReadPdf.node.ts | 0 .../{ => RenameKeys}/RenameKeys.node.json | 0 .../nodes/{ => RenameKeys}/RenameKeys.node.ts | 0 .../RespondToWebhook.node.json | 0 .../RespondToWebhook.node.ts | 0 .../nodes/{ => RespondToWebhook}/webhook.svg | 0 .../{ => RssFeedRead}/RssFeedRead.node.json | 0 .../{ => RssFeedRead}/RssFeedRead.node.ts | 0 packages/nodes-base/nodes/RunAt.node.ts | 346 ------------------ .../nodes-base/nodes/{ => Set}/Set.node.json | 0 .../nodes-base/nodes/{ => Set}/Set.node.ts | 0 .../SplitInBatches.node.json | 0 .../SplitInBatches.node.ts | 0 .../SpreadsheetFile.node.json | 0 .../SpreadsheetFile.node.ts | 0 .../{ => SseTrigger}/SseTrigger.node.json | 0 .../nodes/{ => SseTrigger}/SseTrigger.node.ts | 0 .../nodes/{ => Start}/Start.node.json | 0 .../nodes/{ => Start}/Start.node.ts | 0 .../{ => StopAndError}/StopAndError.node.json | 0 .../{ => StopAndError}/StopAndError.node.ts | 0 .../nodes/{ => Switch}/Switch.node.json | 0 .../nodes/{ => Switch}/Switch.node.ts | 0 .../nodes/{ => Wait}/Wait.node.json | 0 .../nodes-base/nodes/{ => Wait}/Wait.node.ts | 0 .../nodes/{ => Webhook}/Webhook.node.json | 0 .../nodes/{ => Webhook}/Webhook.node.ts | 0 packages/nodes-base/nodes/Webhook/webhook.svg | 1 + .../WorkflowTrigger.node.json | 0 .../WorkflowTrigger.node.ts | 0 .../WriteBinaryFile.node.json | 0 .../WriteBinaryFile.node.ts | 0 .../nodes-base/nodes/{ => Xml}/Xml.node.json | 0 .../nodes-base/nodes/{ => Xml}/Xml.node.ts | 0 packages/nodes-base/package.json | 219 ++++++----- 95 files changed, 109 insertions(+), 472 deletions(-) delete mode 100644 packages/nodes-base/nodes/ActivationTrigger.node.json rename packages/nodes-base/nodes/{ => Compression}/Compression.node.json (100%) rename packages/nodes-base/nodes/{ => Compression}/Compression.node.ts (100%) rename packages/nodes-base/nodes/{ => Cron}/Cron.node.json (100%) rename packages/nodes-base/nodes/{ => Cron}/Cron.node.ts (100%) rename packages/nodes-base/nodes/{ => Crypto}/Crypto.node.json (100%) rename packages/nodes-base/nodes/{ => Crypto}/Crypto.node.ts (100%) rename packages/nodes-base/nodes/{ => DateTime}/DateTime.node.json (100%) rename packages/nodes-base/nodes/{ => DateTime}/DateTime.node.ts (100%) rename packages/nodes-base/nodes/{ => EditImage}/EditImage.node.json (100%) rename packages/nodes-base/nodes/{ => EditImage}/EditImage.node.ts (100%) rename packages/nodes-base/nodes/{ => EmailReadImap}/EmailReadImap.node.json (100%) rename packages/nodes-base/nodes/{ => EmailReadImap}/EmailReadImap.node.ts (100%) rename packages/nodes-base/nodes/{ => EmailSend}/EmailSend.node.json (100%) rename packages/nodes-base/nodes/{ => EmailSend}/EmailSend.node.ts (100%) rename packages/nodes-base/nodes/{ => ErrorTrigger}/ErrorTrigger.node.json (100%) rename packages/nodes-base/nodes/{ => ErrorTrigger}/ErrorTrigger.node.ts (100%) rename packages/nodes-base/nodes/{ => ExecuteCommand}/ExecuteCommand.node.json (100%) rename packages/nodes-base/nodes/{ => ExecuteCommand}/ExecuteCommand.node.ts (100%) rename packages/nodes-base/nodes/{ => ExecuteWorkflow}/ExecuteWorkflow.node.json (100%) rename packages/nodes-base/nodes/{ => ExecuteWorkflow}/ExecuteWorkflow.node.ts (100%) rename packages/nodes-base/nodes/{ => Ftp}/Ftp.node.json (100%) rename packages/nodes-base/nodes/{ => Ftp}/Ftp.node.ts (100%) rename packages/nodes-base/nodes/{ => Function}/Function.node.json (100%) rename packages/nodes-base/nodes/{ => Function}/Function.node.ts (100%) rename packages/nodes-base/nodes/{ => FunctionItem}/FunctionItem.node.json (100%) rename packages/nodes-base/nodes/{ => FunctionItem}/FunctionItem.node.ts (100%) rename packages/nodes-base/nodes/{ => HttpRequest}/HttpRequest.node.json (100%) rename packages/nodes-base/nodes/{ => HttpRequest}/HttpRequest.node.ts (100%) rename packages/nodes-base/nodes/{ => ICalendar}/ICalendar.node.json (100%) rename packages/nodes-base/nodes/{ => ICalendar}/ICalendar.node.ts (100%) rename packages/nodes-base/nodes/{ => If}/If.node.json (100%) rename packages/nodes-base/nodes/{ => If}/If.node.ts (100%) rename packages/nodes-base/nodes/{ => Interval}/Interval.node.json (100%) rename packages/nodes-base/nodes/{ => Interval}/Interval.node.ts (100%) rename packages/nodes-base/nodes/{ => ItemLists}/ItemLists.node.json (100%) rename packages/nodes-base/nodes/{ => ItemLists}/ItemLists.node.ts (100%) rename packages/nodes-base/nodes/{ => ItemLists}/itemLists.svg (100%) rename packages/nodes-base/nodes/{ => LocalFileTrigger}/LocalFileTrigger.node.json (100%) rename packages/nodes-base/nodes/{ => LocalFileTrigger}/LocalFileTrigger.node.ts (100%) rename packages/nodes-base/nodes/{ => Merge}/Merge.node.json (100%) rename packages/nodes-base/nodes/{ => Merge}/Merge.node.ts (100%) rename packages/nodes-base/nodes/{ => MoveBinaryData}/MoveBinaryData.node.json (100%) rename packages/nodes-base/nodes/{ => MoveBinaryData}/MoveBinaryData.node.ts (100%) rename packages/nodes-base/nodes/{ => N8nTrainingCustomerDatastore}/N8nTrainingCustomerDatastore.node.ts (100%) rename packages/nodes-base/nodes/{n8nTrainingCustomerDatastore.svg => N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.svg} (100%) rename packages/nodes-base/nodes/{ => N8nTrainingCustomerMessenger}/N8nTrainingCustomerMessenger.node.ts (100%) rename packages/nodes-base/nodes/{n8nTrainingCustomerMessenger.svg => N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.svg} (100%) rename packages/nodes-base/nodes/{ => N8nTrigger}/N8nTrigger.node.json (100%) rename packages/nodes-base/nodes/{ => N8nTrigger}/N8nTrigger.node.ts (100%) rename packages/nodes-base/nodes/{ => N8nTrigger}/n8nTrigger.svg (100%) rename packages/nodes-base/nodes/{ => NoOp}/NoOp.node.json (100%) rename packages/nodes-base/nodes/{ => NoOp}/NoOp.node.ts (100%) rename packages/nodes-base/nodes/{ => OpenWeatherMap}/OpenWeatherMap.node.json (100%) rename packages/nodes-base/nodes/{ => OpenWeatherMap}/OpenWeatherMap.node.ts (100%) rename packages/nodes-base/nodes/{ => ReadBinaryFile}/ReadBinaryFile.node.json (100%) rename packages/nodes-base/nodes/{ => ReadBinaryFile}/ReadBinaryFile.node.ts (100%) rename packages/nodes-base/nodes/{ => ReadBinaryFiles}/ReadBinaryFiles.node.json (100%) rename packages/nodes-base/nodes/{ => ReadBinaryFiles}/ReadBinaryFiles.node.ts (100%) rename packages/nodes-base/nodes/{ => ReadPdf}/ReadPdf.node.json (100%) rename packages/nodes-base/nodes/{ => ReadPdf}/ReadPdf.node.ts (100%) rename packages/nodes-base/nodes/{ => RenameKeys}/RenameKeys.node.json (100%) rename packages/nodes-base/nodes/{ => RenameKeys}/RenameKeys.node.ts (100%) rename packages/nodes-base/nodes/{ => RespondToWebhook}/RespondToWebhook.node.json (100%) rename packages/nodes-base/nodes/{ => RespondToWebhook}/RespondToWebhook.node.ts (100%) rename packages/nodes-base/nodes/{ => RespondToWebhook}/webhook.svg (100%) rename packages/nodes-base/nodes/{ => RssFeedRead}/RssFeedRead.node.json (100%) rename packages/nodes-base/nodes/{ => RssFeedRead}/RssFeedRead.node.ts (100%) delete mode 100644 packages/nodes-base/nodes/RunAt.node.ts rename packages/nodes-base/nodes/{ => Set}/Set.node.json (100%) rename packages/nodes-base/nodes/{ => Set}/Set.node.ts (100%) rename packages/nodes-base/nodes/{ => SplitInBatches}/SplitInBatches.node.json (100%) rename packages/nodes-base/nodes/{ => SplitInBatches}/SplitInBatches.node.ts (100%) rename packages/nodes-base/nodes/{ => SpreadsheetFile}/SpreadsheetFile.node.json (100%) rename packages/nodes-base/nodes/{ => SpreadsheetFile}/SpreadsheetFile.node.ts (100%) rename packages/nodes-base/nodes/{ => SseTrigger}/SseTrigger.node.json (100%) rename packages/nodes-base/nodes/{ => SseTrigger}/SseTrigger.node.ts (100%) rename packages/nodes-base/nodes/{ => Start}/Start.node.json (100%) rename packages/nodes-base/nodes/{ => Start}/Start.node.ts (100%) rename packages/nodes-base/nodes/{ => StopAndError}/StopAndError.node.json (100%) rename packages/nodes-base/nodes/{ => StopAndError}/StopAndError.node.ts (100%) rename packages/nodes-base/nodes/{ => Switch}/Switch.node.json (100%) rename packages/nodes-base/nodes/{ => Switch}/Switch.node.ts (100%) rename packages/nodes-base/nodes/{ => Wait}/Wait.node.json (100%) rename packages/nodes-base/nodes/{ => Wait}/Wait.node.ts (100%) rename packages/nodes-base/nodes/{ => Webhook}/Webhook.node.json (100%) rename packages/nodes-base/nodes/{ => Webhook}/Webhook.node.ts (100%) create mode 100644 packages/nodes-base/nodes/Webhook/webhook.svg rename packages/nodes-base/nodes/{ => WorkflowTrigger}/WorkflowTrigger.node.json (100%) rename packages/nodes-base/nodes/{ => WorkflowTrigger}/WorkflowTrigger.node.ts (100%) rename packages/nodes-base/nodes/{ => WriteBinaryFile}/WriteBinaryFile.node.json (100%) rename packages/nodes-base/nodes/{ => WriteBinaryFile}/WriteBinaryFile.node.ts (100%) rename packages/nodes-base/nodes/{ => Xml}/Xml.node.json (100%) rename packages/nodes-base/nodes/{ => Xml}/Xml.node.ts (100%) diff --git a/packages/nodes-base/nodes/ActivationTrigger.node.json b/packages/nodes-base/nodes/ActivationTrigger.node.json deleted file mode 100644 index 84ec4f112..000000000 --- a/packages/nodes-base/nodes/ActivationTrigger.node.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "node": "n8n-nodes-base.activationTrigger", - "nodeVersion": "1.0", - "codexVersion": "1.0", - "categories": [ - "Core Nodes" - ], - "resources": { - "primaryDocumentation": [ - { - "url": "https://docs.n8n.io/nodes/n8n-nodes-base.activationTrigger/" - } - ] - } -} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Compression.node.json b/packages/nodes-base/nodes/Compression/Compression.node.json similarity index 100% rename from packages/nodes-base/nodes/Compression.node.json rename to packages/nodes-base/nodes/Compression/Compression.node.json diff --git a/packages/nodes-base/nodes/Compression.node.ts b/packages/nodes-base/nodes/Compression/Compression.node.ts similarity index 100% rename from packages/nodes-base/nodes/Compression.node.ts rename to packages/nodes-base/nodes/Compression/Compression.node.ts diff --git a/packages/nodes-base/nodes/Cron.node.json b/packages/nodes-base/nodes/Cron/Cron.node.json similarity index 100% rename from packages/nodes-base/nodes/Cron.node.json rename to packages/nodes-base/nodes/Cron/Cron.node.json diff --git a/packages/nodes-base/nodes/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts similarity index 100% rename from packages/nodes-base/nodes/Cron.node.ts rename to packages/nodes-base/nodes/Cron/Cron.node.ts diff --git a/packages/nodes-base/nodes/Crypto.node.json b/packages/nodes-base/nodes/Crypto/Crypto.node.json similarity index 100% rename from packages/nodes-base/nodes/Crypto.node.json rename to packages/nodes-base/nodes/Crypto/Crypto.node.json diff --git a/packages/nodes-base/nodes/Crypto.node.ts b/packages/nodes-base/nodes/Crypto/Crypto.node.ts similarity index 100% rename from packages/nodes-base/nodes/Crypto.node.ts rename to packages/nodes-base/nodes/Crypto/Crypto.node.ts diff --git a/packages/nodes-base/nodes/DateTime.node.json b/packages/nodes-base/nodes/DateTime/DateTime.node.json similarity index 100% rename from packages/nodes-base/nodes/DateTime.node.json rename to packages/nodes-base/nodes/DateTime/DateTime.node.json diff --git a/packages/nodes-base/nodes/DateTime.node.ts b/packages/nodes-base/nodes/DateTime/DateTime.node.ts similarity index 100% rename from packages/nodes-base/nodes/DateTime.node.ts rename to packages/nodes-base/nodes/DateTime/DateTime.node.ts diff --git a/packages/nodes-base/nodes/EditImage.node.json b/packages/nodes-base/nodes/EditImage/EditImage.node.json similarity index 100% rename from packages/nodes-base/nodes/EditImage.node.json rename to packages/nodes-base/nodes/EditImage/EditImage.node.json diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage/EditImage.node.ts similarity index 100% rename from packages/nodes-base/nodes/EditImage.node.ts rename to packages/nodes-base/nodes/EditImage/EditImage.node.ts diff --git a/packages/nodes-base/nodes/EmailReadImap.node.json b/packages/nodes-base/nodes/EmailReadImap/EmailReadImap.node.json similarity index 100% rename from packages/nodes-base/nodes/EmailReadImap.node.json rename to packages/nodes-base/nodes/EmailReadImap/EmailReadImap.node.json diff --git a/packages/nodes-base/nodes/EmailReadImap.node.ts b/packages/nodes-base/nodes/EmailReadImap/EmailReadImap.node.ts similarity index 100% rename from packages/nodes-base/nodes/EmailReadImap.node.ts rename to packages/nodes-base/nodes/EmailReadImap/EmailReadImap.node.ts diff --git a/packages/nodes-base/nodes/EmailSend.node.json b/packages/nodes-base/nodes/EmailSend/EmailSend.node.json similarity index 100% rename from packages/nodes-base/nodes/EmailSend.node.json rename to packages/nodes-base/nodes/EmailSend/EmailSend.node.json diff --git a/packages/nodes-base/nodes/EmailSend.node.ts b/packages/nodes-base/nodes/EmailSend/EmailSend.node.ts similarity index 100% rename from packages/nodes-base/nodes/EmailSend.node.ts rename to packages/nodes-base/nodes/EmailSend/EmailSend.node.ts diff --git a/packages/nodes-base/nodes/ErrorTrigger.node.json b/packages/nodes-base/nodes/ErrorTrigger/ErrorTrigger.node.json similarity index 100% rename from packages/nodes-base/nodes/ErrorTrigger.node.json rename to packages/nodes-base/nodes/ErrorTrigger/ErrorTrigger.node.json diff --git a/packages/nodes-base/nodes/ErrorTrigger.node.ts b/packages/nodes-base/nodes/ErrorTrigger/ErrorTrigger.node.ts similarity index 100% rename from packages/nodes-base/nodes/ErrorTrigger.node.ts rename to packages/nodes-base/nodes/ErrorTrigger/ErrorTrigger.node.ts diff --git a/packages/nodes-base/nodes/ExecuteCommand.node.json b/packages/nodes-base/nodes/ExecuteCommand/ExecuteCommand.node.json similarity index 100% rename from packages/nodes-base/nodes/ExecuteCommand.node.json rename to packages/nodes-base/nodes/ExecuteCommand/ExecuteCommand.node.json diff --git a/packages/nodes-base/nodes/ExecuteCommand.node.ts b/packages/nodes-base/nodes/ExecuteCommand/ExecuteCommand.node.ts similarity index 100% rename from packages/nodes-base/nodes/ExecuteCommand.node.ts rename to packages/nodes-base/nodes/ExecuteCommand/ExecuteCommand.node.ts diff --git a/packages/nodes-base/nodes/ExecuteWorkflow.node.json b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.json similarity index 100% rename from packages/nodes-base/nodes/ExecuteWorkflow.node.json rename to packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.json diff --git a/packages/nodes-base/nodes/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts similarity index 100% rename from packages/nodes-base/nodes/ExecuteWorkflow.node.ts rename to packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts diff --git a/packages/nodes-base/nodes/Ftp.node.json b/packages/nodes-base/nodes/Ftp/Ftp.node.json similarity index 100% rename from packages/nodes-base/nodes/Ftp.node.json rename to packages/nodes-base/nodes/Ftp/Ftp.node.json diff --git a/packages/nodes-base/nodes/Ftp.node.ts b/packages/nodes-base/nodes/Ftp/Ftp.node.ts similarity index 100% rename from packages/nodes-base/nodes/Ftp.node.ts rename to packages/nodes-base/nodes/Ftp/Ftp.node.ts diff --git a/packages/nodes-base/nodes/Function.node.json b/packages/nodes-base/nodes/Function/Function.node.json similarity index 100% rename from packages/nodes-base/nodes/Function.node.json rename to packages/nodes-base/nodes/Function/Function.node.json diff --git a/packages/nodes-base/nodes/Function.node.ts b/packages/nodes-base/nodes/Function/Function.node.ts similarity index 100% rename from packages/nodes-base/nodes/Function.node.ts rename to packages/nodes-base/nodes/Function/Function.node.ts diff --git a/packages/nodes-base/nodes/FunctionItem.node.json b/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.json similarity index 100% rename from packages/nodes-base/nodes/FunctionItem.node.json rename to packages/nodes-base/nodes/FunctionItem/FunctionItem.node.json diff --git a/packages/nodes-base/nodes/FunctionItem.node.ts b/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts similarity index 100% rename from packages/nodes-base/nodes/FunctionItem.node.ts rename to packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts diff --git a/packages/nodes-base/nodes/HttpRequest.node.json b/packages/nodes-base/nodes/HttpRequest/HttpRequest.node.json similarity index 100% rename from packages/nodes-base/nodes/HttpRequest.node.json rename to packages/nodes-base/nodes/HttpRequest/HttpRequest.node.json diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest/HttpRequest.node.ts similarity index 100% rename from packages/nodes-base/nodes/HttpRequest.node.ts rename to packages/nodes-base/nodes/HttpRequest/HttpRequest.node.ts diff --git a/packages/nodes-base/nodes/ICalendar.node.json b/packages/nodes-base/nodes/ICalendar/ICalendar.node.json similarity index 100% rename from packages/nodes-base/nodes/ICalendar.node.json rename to packages/nodes-base/nodes/ICalendar/ICalendar.node.json diff --git a/packages/nodes-base/nodes/ICalendar.node.ts b/packages/nodes-base/nodes/ICalendar/ICalendar.node.ts similarity index 100% rename from packages/nodes-base/nodes/ICalendar.node.ts rename to packages/nodes-base/nodes/ICalendar/ICalendar.node.ts diff --git a/packages/nodes-base/nodes/If.node.json b/packages/nodes-base/nodes/If/If.node.json similarity index 100% rename from packages/nodes-base/nodes/If.node.json rename to packages/nodes-base/nodes/If/If.node.json diff --git a/packages/nodes-base/nodes/If.node.ts b/packages/nodes-base/nodes/If/If.node.ts similarity index 100% rename from packages/nodes-base/nodes/If.node.ts rename to packages/nodes-base/nodes/If/If.node.ts diff --git a/packages/nodes-base/nodes/Interval.node.json b/packages/nodes-base/nodes/Interval/Interval.node.json similarity index 100% rename from packages/nodes-base/nodes/Interval.node.json rename to packages/nodes-base/nodes/Interval/Interval.node.json diff --git a/packages/nodes-base/nodes/Interval.node.ts b/packages/nodes-base/nodes/Interval/Interval.node.ts similarity index 100% rename from packages/nodes-base/nodes/Interval.node.ts rename to packages/nodes-base/nodes/Interval/Interval.node.ts diff --git a/packages/nodes-base/nodes/ItemLists.node.json b/packages/nodes-base/nodes/ItemLists/ItemLists.node.json similarity index 100% rename from packages/nodes-base/nodes/ItemLists.node.json rename to packages/nodes-base/nodes/ItemLists/ItemLists.node.json diff --git a/packages/nodes-base/nodes/ItemLists.node.ts b/packages/nodes-base/nodes/ItemLists/ItemLists.node.ts similarity index 100% rename from packages/nodes-base/nodes/ItemLists.node.ts rename to packages/nodes-base/nodes/ItemLists/ItemLists.node.ts diff --git a/packages/nodes-base/nodes/itemLists.svg b/packages/nodes-base/nodes/ItemLists/itemLists.svg similarity index 100% rename from packages/nodes-base/nodes/itemLists.svg rename to packages/nodes-base/nodes/ItemLists/itemLists.svg diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.json b/packages/nodes-base/nodes/LocalFileTrigger/LocalFileTrigger.node.json similarity index 100% rename from packages/nodes-base/nodes/LocalFileTrigger.node.json rename to packages/nodes-base/nodes/LocalFileTrigger/LocalFileTrigger.node.json diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.ts b/packages/nodes-base/nodes/LocalFileTrigger/LocalFileTrigger.node.ts similarity index 100% rename from packages/nodes-base/nodes/LocalFileTrigger.node.ts rename to packages/nodes-base/nodes/LocalFileTrigger/LocalFileTrigger.node.ts diff --git a/packages/nodes-base/nodes/Merge.node.json b/packages/nodes-base/nodes/Merge/Merge.node.json similarity index 100% rename from packages/nodes-base/nodes/Merge.node.json rename to packages/nodes-base/nodes/Merge/Merge.node.json diff --git a/packages/nodes-base/nodes/Merge.node.ts b/packages/nodes-base/nodes/Merge/Merge.node.ts similarity index 100% rename from packages/nodes-base/nodes/Merge.node.ts rename to packages/nodes-base/nodes/Merge/Merge.node.ts diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.json b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.json similarity index 100% rename from packages/nodes-base/nodes/MoveBinaryData.node.json rename to packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.json diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts similarity index 100% rename from packages/nodes-base/nodes/MoveBinaryData.node.ts rename to packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts diff --git a/packages/nodes-base/nodes/N8nTrainingCustomerDatastore.node.ts b/packages/nodes-base/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.node.ts similarity index 100% rename from packages/nodes-base/nodes/N8nTrainingCustomerDatastore.node.ts rename to packages/nodes-base/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.node.ts diff --git a/packages/nodes-base/nodes/n8nTrainingCustomerDatastore.svg b/packages/nodes-base/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.svg similarity index 100% rename from packages/nodes-base/nodes/n8nTrainingCustomerDatastore.svg rename to packages/nodes-base/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.svg diff --git a/packages/nodes-base/nodes/N8nTrainingCustomerMessenger.node.ts b/packages/nodes-base/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.node.ts similarity index 100% rename from packages/nodes-base/nodes/N8nTrainingCustomerMessenger.node.ts rename to packages/nodes-base/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.node.ts diff --git a/packages/nodes-base/nodes/n8nTrainingCustomerMessenger.svg b/packages/nodes-base/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.svg similarity index 100% rename from packages/nodes-base/nodes/n8nTrainingCustomerMessenger.svg rename to packages/nodes-base/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.svg diff --git a/packages/nodes-base/nodes/N8nTrigger.node.json b/packages/nodes-base/nodes/N8nTrigger/N8nTrigger.node.json similarity index 100% rename from packages/nodes-base/nodes/N8nTrigger.node.json rename to packages/nodes-base/nodes/N8nTrigger/N8nTrigger.node.json diff --git a/packages/nodes-base/nodes/N8nTrigger.node.ts b/packages/nodes-base/nodes/N8nTrigger/N8nTrigger.node.ts similarity index 100% rename from packages/nodes-base/nodes/N8nTrigger.node.ts rename to packages/nodes-base/nodes/N8nTrigger/N8nTrigger.node.ts diff --git a/packages/nodes-base/nodes/n8nTrigger.svg b/packages/nodes-base/nodes/N8nTrigger/n8nTrigger.svg similarity index 100% rename from packages/nodes-base/nodes/n8nTrigger.svg rename to packages/nodes-base/nodes/N8nTrigger/n8nTrigger.svg diff --git a/packages/nodes-base/nodes/NoOp.node.json b/packages/nodes-base/nodes/NoOp/NoOp.node.json similarity index 100% rename from packages/nodes-base/nodes/NoOp.node.json rename to packages/nodes-base/nodes/NoOp/NoOp.node.json diff --git a/packages/nodes-base/nodes/NoOp.node.ts b/packages/nodes-base/nodes/NoOp/NoOp.node.ts similarity index 100% rename from packages/nodes-base/nodes/NoOp.node.ts rename to packages/nodes-base/nodes/NoOp/NoOp.node.ts diff --git a/packages/nodes-base/nodes/OpenWeatherMap.node.json b/packages/nodes-base/nodes/OpenWeatherMap/OpenWeatherMap.node.json similarity index 100% rename from packages/nodes-base/nodes/OpenWeatherMap.node.json rename to packages/nodes-base/nodes/OpenWeatherMap/OpenWeatherMap.node.json diff --git a/packages/nodes-base/nodes/OpenWeatherMap.node.ts b/packages/nodes-base/nodes/OpenWeatherMap/OpenWeatherMap.node.ts similarity index 100% rename from packages/nodes-base/nodes/OpenWeatherMap.node.ts rename to packages/nodes-base/nodes/OpenWeatherMap/OpenWeatherMap.node.ts diff --git a/packages/nodes-base/nodes/ReadBinaryFile.node.json b/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.json similarity index 100% rename from packages/nodes-base/nodes/ReadBinaryFile.node.json rename to packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.json diff --git a/packages/nodes-base/nodes/ReadBinaryFile.node.ts b/packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts similarity index 100% rename from packages/nodes-base/nodes/ReadBinaryFile.node.ts rename to packages/nodes-base/nodes/ReadBinaryFile/ReadBinaryFile.node.ts diff --git a/packages/nodes-base/nodes/ReadBinaryFiles.node.json b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.json similarity index 100% rename from packages/nodes-base/nodes/ReadBinaryFiles.node.json rename to packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.json diff --git a/packages/nodes-base/nodes/ReadBinaryFiles.node.ts b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts similarity index 100% rename from packages/nodes-base/nodes/ReadBinaryFiles.node.ts rename to packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts diff --git a/packages/nodes-base/nodes/ReadPdf.node.json b/packages/nodes-base/nodes/ReadPdf/ReadPdf.node.json similarity index 100% rename from packages/nodes-base/nodes/ReadPdf.node.json rename to packages/nodes-base/nodes/ReadPdf/ReadPdf.node.json diff --git a/packages/nodes-base/nodes/ReadPdf.node.ts b/packages/nodes-base/nodes/ReadPdf/ReadPdf.node.ts similarity index 100% rename from packages/nodes-base/nodes/ReadPdf.node.ts rename to packages/nodes-base/nodes/ReadPdf/ReadPdf.node.ts diff --git a/packages/nodes-base/nodes/RenameKeys.node.json b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.json similarity index 100% rename from packages/nodes-base/nodes/RenameKeys.node.json rename to packages/nodes-base/nodes/RenameKeys/RenameKeys.node.json diff --git a/packages/nodes-base/nodes/RenameKeys.node.ts b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts similarity index 100% rename from packages/nodes-base/nodes/RenameKeys.node.ts rename to packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.json b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.json similarity index 100% rename from packages/nodes-base/nodes/RespondToWebhook.node.json rename to packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.json diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts similarity index 100% rename from packages/nodes-base/nodes/RespondToWebhook.node.ts rename to packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts diff --git a/packages/nodes-base/nodes/webhook.svg b/packages/nodes-base/nodes/RespondToWebhook/webhook.svg similarity index 100% rename from packages/nodes-base/nodes/webhook.svg rename to packages/nodes-base/nodes/RespondToWebhook/webhook.svg diff --git a/packages/nodes-base/nodes/RssFeedRead.node.json b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.json similarity index 100% rename from packages/nodes-base/nodes/RssFeedRead.node.json rename to packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.json diff --git a/packages/nodes-base/nodes/RssFeedRead.node.ts b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts similarity index 100% rename from packages/nodes-base/nodes/RssFeedRead.node.ts rename to packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts diff --git a/packages/nodes-base/nodes/RunAt.node.ts b/packages/nodes-base/nodes/RunAt.node.ts deleted file mode 100644 index b96d9b22e..000000000 --- a/packages/nodes-base/nodes/RunAt.node.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { ITriggerFunctions } from 'n8n-core'; -import { - INodeType, - INodeTypeDescription, - ITriggerResponse, -} from 'n8n-workflow'; - - -export class RunAt implements INodeType { - description: INodeTypeDescription = { - displayName: 'RunAt', - name: 'runAt', - icon: 'fa:calendar', - group: ['trigger'], - version: 1, - description: 'Triggers the workflow at a specific time', - defaults: { - name: 'RunAt', - color: '#00FF00', - }, - inputs: [], - outputs: ['main'], - properties: [ - { - displayName: 'Trigger Times', - name: 'triggerTimes', - type: 'dateTime', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Time', - }, - default: '', - // default: [], - description: 'Triggers for the workflow', - placeholder: 'Add Time', - }, - // { - // displayName: 'Trigger Times', - // name: 'triggerTimes', - // type: 'fixedCollection', - // typeOptions: { - // multipleValues: true, - // multipleValueButtonText: 'Add Time', - // }, - // default: {}, - // description: 'Triggers for the workflow', - // placeholder: 'Add Cron Time', - // options: [ - // { - // name: 'item', - // displayName: 'Item', - // values: [ - // { - // displayName: 'Mode', - // name: 'mode', - // type: 'options', - // options: [ - // { - // name: 'Every Minute', - // value: 'everyMinute', - // }, - // { - // name: 'Every Hour', - // value: 'everyHour', - // }, - // { - // name: 'Every Day', - // value: 'everyDay', - // }, - // { - // name: 'Every Week', - // value: 'everyWeek', - // }, - // { - // name: 'Every Month', - // value: 'everyMonth', - // }, - // { - // name: 'Every X', - // value: 'everyX', - // }, - // { - // name: 'Custom', - // value: 'custom', - // }, - // ], - // default: 'everyDay', - // description: 'How often to trigger.', - // }, - // { - // displayName: 'Hour', - // name: 'hour', - // type: 'number', - // typeOptions: { - // minValue: 0, - // maxValue: 23, - // }, - // displayOptions: { - // hide: { - // mode: [ - // 'custom', - // 'everyHour', - // 'everyMinute', - // 'everyX', - // ], - // }, - // }, - // default: 14, - // description: 'The hour of the day to trigger (24h format).', - // }, - // { - // displayName: 'Minute', - // name: 'minute', - // type: 'number', - // typeOptions: { - // minValue: 0, - // maxValue: 59, - // }, - // displayOptions: { - // hide: { - // mode: [ - // 'custom', - // 'everyMinute', - // 'everyX', - // ], - // }, - // }, - // default: 0, - // description: 'The minute of the day to trigger.', - // }, - // { - // displayName: 'Day of Month', - // name: 'dayOfMonth', - // type: 'number', - // displayOptions: { - // show: { - // mode: [ - // 'everyMonth', - // ], - // }, - // }, - // typeOptions: { - // minValue: 1, - // maxValue: 31, - // }, - // default: 1, - // description: 'The day of the month to trigger.', - // }, - // { - // displayName: 'Weekday', - // name: 'weekday', - // type: 'options', - // displayOptions: { - // show: { - // mode: [ - // 'everyWeek', - // ], - // }, - // }, - // options: [ - // { - // name: 'Monday', - // value: '1', - // }, - // { - // name: 'Tuesday', - // value: '2', - // }, - // { - // name: 'Wednesday', - // value: '3', - // }, - // { - // name: 'Thursday', - // value: '4', - // }, - // { - // name: 'Friday', - // value: '5', - // }, - // { - // name: 'Saturday', - // value: '6', - // }, - // { - // name: 'Sunday', - // value: '0', - // }, - // ], - // default: '1', - // description: 'The weekday to trigger.', - // }, - // { - // displayName: 'Cron Expression', - // name: 'cronExpression', - // type: 'string', - // displayOptions: { - // show: { - // mode: [ - // 'custom', - // ], - // }, - // }, - // default: '* * * * * *', - // description: 'Use custom cron expression. Values and ranges as follows:
  • Seconds: 0-59
  • Minutes: 0 - 59
  • Hours: 0 - 23
  • Day of Month: 1 - 31
  • Months: 0 - 11 (Jan - Dec)
  • Day of Week: 0 - 6 (Sun - Sat)
', - // }, - // { - // displayName: 'Value', - // name: 'value', - // type: 'number', - // typeOptions: { - // minValue: 0, - // maxValue: 1000, - // }, - // displayOptions: { - // show: { - // mode: [ - // 'everyX', - // ], - // }, - // }, - // default: 2, - // description: 'All how many X minutes/hours it should trigger.', - // }, - // { - // displayName: 'Unit', - // name: 'unit', - // type: 'options', - // displayOptions: { - // show: { - // mode: [ - // 'everyX', - // ], - // }, - // }, - // options: [ - // { - // name: 'Minutes', - // value: 'minutes' - // }, - // { - // name: 'Hours', - // value: 'hours' - // }, - // ], - // default: 'hours', - // description: 'If it should trigger all X minutes or hours.', - // }, - // ] - // }, - // ], - // } - ], - }; - - - - async trigger(this: ITriggerFunctions): Promise { - - // const triggerTimes = this.getNodeParameter('triggerTimes') as unknown as { - // item: TriggerTime[]; - // }; - - // // Define the order the cron-time-parameter appear - // const parameterOrder = [ - // 'second', // 0 - 59 - // 'minute', // 0 - 59 - // 'hour', // 0 - 23 - // 'dayOfMonth', // 1 - 31 - // 'month', // 0 - 11(Jan - Dec) - // 'weekday', // 0 - 6(Sun - Sat) - // ]; - - // // Get all the trigger times - // const cronTimes: string[] = []; - // let cronTime: string[]; - // let parameterName: string; - // if (triggerTimes.item !== undefined) { - // for (const item of triggerTimes.item) { - // cronTime = []; - // if (item.mode === 'custom') { - // cronTimes.push(item.cronExpression as string); - // continue; - // } - // if (item.mode === 'everyMinute') { - // cronTimes.push(`${Math.floor(Math.random() * 60).toString()} * * * * *`); - // continue; - // } - // if (item.mode === 'everyX') { - // if (item.unit === 'minutes') { - // cronTimes.push(`${Math.floor(Math.random() * 60).toString()} */${item.value} * * * *`); - // } else if (item.unit === 'hours') { - // cronTimes.push(`${Math.floor(Math.random() * 60).toString()} 0 */${item.value} * * *`); - // } - // continue; - // } - - // for (parameterName of parameterOrder) { - // if (item[parameterName] !== undefined) { - // // Value is set so use it - // cronTime.push(item[parameterName] as string); - // } else if (parameterName === 'second') { - // // For seconds we use by default a random one to make sure to - // // balance the load a little bit over time - // cronTime.push(Math.floor(Math.random() * 60).toString()); - // } else { - // // For all others set "any" - // cronTime.push('*'); - // } - // } - - // cronTimes.push(cronTime.join(' ')); - // } - // } - - // // The trigger function to execute when the cron-time got reached - // // or when manually triggered - // const executeTrigger = () => { - // this.emit([this.helpers.returnJsonArray([{}])]); - // }; - - // const timezone = this.getTimezone(); - - // // Start the cron-jobs - // const cronJobs: CronJob[] = []; - // for (const cronTime of cronTimes) { - // cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone)); - // } - - // // Stop the cron-jobs - // async function closeFunction() { - // for (const cronJob of cronJobs) { - // cronJob.stop(); - // } - // } - - // async function manualTriggerFunction() { - // executeTrigger(); - // } - - return { - // closeFunction, - // manualTriggerFunction, - }; - } -} diff --git a/packages/nodes-base/nodes/Set.node.json b/packages/nodes-base/nodes/Set/Set.node.json similarity index 100% rename from packages/nodes-base/nodes/Set.node.json rename to packages/nodes-base/nodes/Set/Set.node.json diff --git a/packages/nodes-base/nodes/Set.node.ts b/packages/nodes-base/nodes/Set/Set.node.ts similarity index 100% rename from packages/nodes-base/nodes/Set.node.ts rename to packages/nodes-base/nodes/Set/Set.node.ts diff --git a/packages/nodes-base/nodes/SplitInBatches.node.json b/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.json similarity index 100% rename from packages/nodes-base/nodes/SplitInBatches.node.json rename to packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.json diff --git a/packages/nodes-base/nodes/SplitInBatches.node.ts b/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts similarity index 100% rename from packages/nodes-base/nodes/SplitInBatches.node.ts rename to packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts diff --git a/packages/nodes-base/nodes/SpreadsheetFile.node.json b/packages/nodes-base/nodes/SpreadsheetFile/SpreadsheetFile.node.json similarity index 100% rename from packages/nodes-base/nodes/SpreadsheetFile.node.json rename to packages/nodes-base/nodes/SpreadsheetFile/SpreadsheetFile.node.json diff --git a/packages/nodes-base/nodes/SpreadsheetFile.node.ts b/packages/nodes-base/nodes/SpreadsheetFile/SpreadsheetFile.node.ts similarity index 100% rename from packages/nodes-base/nodes/SpreadsheetFile.node.ts rename to packages/nodes-base/nodes/SpreadsheetFile/SpreadsheetFile.node.ts diff --git a/packages/nodes-base/nodes/SseTrigger.node.json b/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.json similarity index 100% rename from packages/nodes-base/nodes/SseTrigger.node.json rename to packages/nodes-base/nodes/SseTrigger/SseTrigger.node.json diff --git a/packages/nodes-base/nodes/SseTrigger.node.ts b/packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts similarity index 100% rename from packages/nodes-base/nodes/SseTrigger.node.ts rename to packages/nodes-base/nodes/SseTrigger/SseTrigger.node.ts diff --git a/packages/nodes-base/nodes/Start.node.json b/packages/nodes-base/nodes/Start/Start.node.json similarity index 100% rename from packages/nodes-base/nodes/Start.node.json rename to packages/nodes-base/nodes/Start/Start.node.json diff --git a/packages/nodes-base/nodes/Start.node.ts b/packages/nodes-base/nodes/Start/Start.node.ts similarity index 100% rename from packages/nodes-base/nodes/Start.node.ts rename to packages/nodes-base/nodes/Start/Start.node.ts diff --git a/packages/nodes-base/nodes/StopAndError.node.json b/packages/nodes-base/nodes/StopAndError/StopAndError.node.json similarity index 100% rename from packages/nodes-base/nodes/StopAndError.node.json rename to packages/nodes-base/nodes/StopAndError/StopAndError.node.json diff --git a/packages/nodes-base/nodes/StopAndError.node.ts b/packages/nodes-base/nodes/StopAndError/StopAndError.node.ts similarity index 100% rename from packages/nodes-base/nodes/StopAndError.node.ts rename to packages/nodes-base/nodes/StopAndError/StopAndError.node.ts diff --git a/packages/nodes-base/nodes/Switch.node.json b/packages/nodes-base/nodes/Switch/Switch.node.json similarity index 100% rename from packages/nodes-base/nodes/Switch.node.json rename to packages/nodes-base/nodes/Switch/Switch.node.json diff --git a/packages/nodes-base/nodes/Switch.node.ts b/packages/nodes-base/nodes/Switch/Switch.node.ts similarity index 100% rename from packages/nodes-base/nodes/Switch.node.ts rename to packages/nodes-base/nodes/Switch/Switch.node.ts diff --git a/packages/nodes-base/nodes/Wait.node.json b/packages/nodes-base/nodes/Wait/Wait.node.json similarity index 100% rename from packages/nodes-base/nodes/Wait.node.json rename to packages/nodes-base/nodes/Wait/Wait.node.json diff --git a/packages/nodes-base/nodes/Wait.node.ts b/packages/nodes-base/nodes/Wait/Wait.node.ts similarity index 100% rename from packages/nodes-base/nodes/Wait.node.ts rename to packages/nodes-base/nodes/Wait/Wait.node.ts diff --git a/packages/nodes-base/nodes/Webhook.node.json b/packages/nodes-base/nodes/Webhook/Webhook.node.json similarity index 100% rename from packages/nodes-base/nodes/Webhook.node.json rename to packages/nodes-base/nodes/Webhook/Webhook.node.json diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts similarity index 100% rename from packages/nodes-base/nodes/Webhook.node.ts rename to packages/nodes-base/nodes/Webhook/Webhook.node.ts diff --git a/packages/nodes-base/nodes/Webhook/webhook.svg b/packages/nodes-base/nodes/Webhook/webhook.svg new file mode 100644 index 000000000..a59b31855 --- /dev/null +++ b/packages/nodes-base/nodes/Webhook/webhook.svg @@ -0,0 +1 @@ + diff --git a/packages/nodes-base/nodes/WorkflowTrigger.node.json b/packages/nodes-base/nodes/WorkflowTrigger/WorkflowTrigger.node.json similarity index 100% rename from packages/nodes-base/nodes/WorkflowTrigger.node.json rename to packages/nodes-base/nodes/WorkflowTrigger/WorkflowTrigger.node.json diff --git a/packages/nodes-base/nodes/WorkflowTrigger.node.ts b/packages/nodes-base/nodes/WorkflowTrigger/WorkflowTrigger.node.ts similarity index 100% rename from packages/nodes-base/nodes/WorkflowTrigger.node.ts rename to packages/nodes-base/nodes/WorkflowTrigger/WorkflowTrigger.node.ts diff --git a/packages/nodes-base/nodes/WriteBinaryFile.node.json b/packages/nodes-base/nodes/WriteBinaryFile/WriteBinaryFile.node.json similarity index 100% rename from packages/nodes-base/nodes/WriteBinaryFile.node.json rename to packages/nodes-base/nodes/WriteBinaryFile/WriteBinaryFile.node.json diff --git a/packages/nodes-base/nodes/WriteBinaryFile.node.ts b/packages/nodes-base/nodes/WriteBinaryFile/WriteBinaryFile.node.ts similarity index 100% rename from packages/nodes-base/nodes/WriteBinaryFile.node.ts rename to packages/nodes-base/nodes/WriteBinaryFile/WriteBinaryFile.node.ts diff --git a/packages/nodes-base/nodes/Xml.node.json b/packages/nodes-base/nodes/Xml/Xml.node.json similarity index 100% rename from packages/nodes-base/nodes/Xml.node.json rename to packages/nodes-base/nodes/Xml/Xml.node.json diff --git a/packages/nodes-base/nodes/Xml.node.ts b/packages/nodes-base/nodes/Xml/Xml.node.ts similarity index 100% rename from packages/nodes-base/nodes/Xml.node.ts rename to packages/nodes-base/nodes/Xml/Xml.node.ts diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index b17229cd9..4047ea247 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -31,21 +31,21 @@ "credentials": [ "dist/credentials/ActionNetworkApi.credentials.js", "dist/credentials/ActiveCampaignApi.credentials.js", - "dist/credentials/AgileCrmApi.credentials.js", "dist/credentials/AcuitySchedulingApi.credentials.js", "dist/credentials/AcuitySchedulingOAuth2Api.credentials.js", + "dist/credentials/AffinityApi.credentials.js", + "dist/credentials/AgileCrmApi.credentials.js", "dist/credentials/AirtableApi.credentials.js", "dist/credentials/Amqp.credentials.js", + "dist/credentials/ApiTemplateIoApi.credentials.js", "dist/credentials/AsanaApi.credentials.js", "dist/credentials/AsanaOAuth2Api.credentials.js", - "dist/credentials/ApiTemplateIoApi.credentials.js", "dist/credentials/AutomizyApi.credentials.js", "dist/credentials/AutopilotApi.credentials.js", "dist/credentials/Aws.credentials.js", - "dist/credentials/AffinityApi.credentials.js", "dist/credentials/BannerbearApi.credentials.js", - "dist/credentials/BeeminderApi.credentials.js", "dist/credentials/BaserowApi.credentials.js", + "dist/credentials/BeeminderApi.credentials.js", "dist/credentials/BitbucketApi.credentials.js", "dist/credentials/BitlyApi.credentials.js", "dist/credentials/BitlyOAuth2Api.credentials.js", @@ -53,6 +53,7 @@ "dist/credentials/BoxOAuth2Api.credentials.js", "dist/credentials/BrandfetchApi.credentials.js", "dist/credentials/BubbleApi.credentials.js", + "dist/credentials/CalendlyApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/CircleCiApi.credentials.js", "dist/credentials/CiscoWebexOAuth2Api.credentials.js", @@ -66,10 +67,8 @@ "dist/credentials/ConvertKitApi.credentials.js", "dist/credentials/CopperApi.credentials.js", "dist/credentials/CortexApi.credentials.js", - "dist/credentials/CalendlyApi.credentials.js", - "dist/credentials/CustomerIoApi.credentials.js", - "dist/credentials/S3.credentials.js", "dist/credentials/CrateDb.credentials.js", + "dist/credentials/CustomerIoApi.credentials.js", "dist/credentials/DeepLApi.credentials.js", "dist/credentials/DemioApi.credentials.js", "dist/credentials/DiscourseApi.credentials.js", @@ -88,33 +87,33 @@ "dist/credentials/EventbriteOAuth2Api.credentials.js", "dist/credentials/FacebookGraphApi.credentials.js", "dist/credentials/FacebookGraphAppApi.credentials.js", + "dist/credentials/FileMaker.credentials.js", + "dist/credentials/FlowApi.credentials.js", + "dist/credentials/FormIoApi.credentials.js", + "dist/credentials/FormstackApi.credentials.js", + "dist/credentials/FormstackOAuth2Api.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", "dist/credentials/FreshserviceApi.credentials.js", "dist/credentials/FreshworksCrmApi.credentials.js", - "dist/credentials/FileMaker.credentials.js", - "dist/credentials/FlowApi.credentials.js", - "dist/credentials/FormstackApi.credentials.js", - "dist/credentials/FormstackOAuth2Api.credentials.js", "dist/credentials/Ftp.credentials.js", - "dist/credentials/FormIoApi.credentials.js", "dist/credentials/GetResponseApi.credentials.js", "dist/credentials/GetResponseOAuth2Api.credentials.js", "dist/credentials/GhostAdminApi.credentials.js", "dist/credentials/GhostContentApi.credentials.js", - "dist/credentials/GitPassword.credentials.js", "dist/credentials/GithubApi.credentials.js", "dist/credentials/GithubOAuth2Api.credentials.js", "dist/credentials/GitlabApi.credentials.js", "dist/credentials/GitlabOAuth2Api.credentials.js", + "dist/credentials/GitPassword.credentials.js", "dist/credentials/GmailOAuth2Api.credentials.js", "dist/credentials/GoogleAnalyticsOAuth2Api.credentials.js", "dist/credentials/GoogleApi.credentials.js", "dist/credentials/GoogleBigQueryOAuth2Api.credentials.js", "dist/credentials/GoogleBooksOAuth2Api.credentials.js", "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", + "dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js", "dist/credentials/GoogleContactsOAuth2Api.credentials.js", "dist/credentials/GoogleDocsOAuth2Api.credentials.js", - "dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js", "dist/credentials/GoogleDriveOAuth2Api.credentials.js", "dist/credentials/GoogleFirebaseCloudFirestoreOAuth2Api.credentials.js", "dist/credentials/GoogleFirebaseRealtimeDatabaseOAuth2Api.credentials.js", @@ -122,13 +121,12 @@ "dist/credentials/GooglePerspectiveOAuth2Api.credentials.js", "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", "dist/credentials/GoogleSlidesOAuth2Api.credentials.js", - "dist/credentials/GSuiteAdminOAuth2Api.credentials.js", "dist/credentials/GoogleTasksOAuth2Api.credentials.js", "dist/credentials/GoogleTranslateOAuth2Api.credentials.js", "dist/credentials/GotifyApi.credentials.js", "dist/credentials/GoToWebinarOAuth2Api.credentials.js", "dist/credentials/GristApi.credentials.js", - "dist/credentials/YouTubeOAuth2Api.credentials.js", + "dist/credentials/GSuiteAdminOAuth2Api.credentials.js", "dist/credentials/GumroadApi.credentials.js", "dist/credentials/HarvestApi.credentials.js", "dist/credentials/HarvestOAuth2Api.credentials.js", @@ -142,10 +140,10 @@ "dist/credentials/HubspotOAuth2Api.credentials.js", "dist/credentials/HumanticAiApi.credentials.js", "dist/credentials/HunterApi.credentials.js", - "dist/credentials/IterableApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", "dist/credentials/InvoiceNinjaApi.credentials.js", + "dist/credentials/IterableApi.credentials.js", "dist/credentials/JiraSoftwareCloudApi.credentials.js", "dist/credentials/JiraSoftwareServerApi.credentials.js", "dist/credentials/JotFormApi.credentials.js", @@ -157,10 +155,10 @@ "dist/credentials/LingvaNexApi.credentials.js", "dist/credentials/LinkedInOAuth2Api.credentials.js", "dist/credentials/Magento2Api.credentials.js", - "dist/credentials/MailerLiteApi.credentials.js", "dist/credentials/MailcheckApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailchimpOAuth2Api.credentials.js", + "dist/credentials/MailerLiteApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MailjetEmailApi.credentials.js", "dist/credentials/MailjetSmsApi.credentials.js", @@ -181,14 +179,14 @@ "dist/credentials/MicrosoftSql.credentials.js", "dist/credentials/MicrosoftTeamsOAuth2Api.credentials.js", "dist/credentials/MicrosoftToDoOAuth2Api.credentials.js", - "dist/credentials/MindeeReceiptApi.credentials.js", "dist/credentials/MindeeInvoiceApi.credentials.js", + "dist/credentials/MindeeReceiptApi.credentials.js", "dist/credentials/MispApi.credentials.js", - "dist/credentials/MonicaCrmApi.credentials.js", "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", "dist/credentials/MondayComOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", + "dist/credentials/MonicaCrmApi.credentials.js", "dist/credentials/Mqtt.credentials.js", "dist/credentials/Msg91Api.credentials.js", "dist/credentials/MySql.credentials.js", @@ -211,17 +209,17 @@ "dist/credentials/PayPalApi.credentials.js", "dist/credentials/PeekalinkApi.credentials.js", "dist/credentials/PhantombusterApi.credentials.js", + "dist/credentials/PhilipsHueOAuth2Api.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/PipedriveOAuth2Api.credentials.js", - "dist/credentials/PhilipsHueOAuth2Api.credentials.js", "dist/credentials/PlivoApi.credentials.js", "dist/credentials/Postgres.credentials.js", "dist/credentials/PostHogApi.credentials.js", "dist/credentials/PostmarkApi.credentials.js", "dist/credentials/ProfitWellApi.credentials.js", "dist/credentials/PushbulletOAuth2Api.credentials.js", - "dist/credentials/PushoverApi.credentials.js", "dist/credentials/PushcutApi.credentials.js", + "dist/credentials/PushoverApi.credentials.js", "dist/credentials/QuestDb.credentials.js", "dist/credentials/QuickBaseApi.credentials.js", "dist/credentials/QuickBooksOAuth2Api.credentials.js", @@ -231,41 +229,40 @@ "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/RundeckApi.credentials.js", + "dist/credentials/S3.credentials.js", "dist/credentials/SalesforceJwtApi.credentials.js", "dist/credentials/SalesforceOAuth2Api.credentials.js", "dist/credentials/SalesmateApi.credentials.js", + "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SeaTableApi.credentials.js", "dist/credentials/SecurityScorecardApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", "dist/credentials/SendGridApi.credentials.js", "dist/credentials/SendyApi.credentials.js", "dist/credentials/SentryIoApi.credentials.js", - "dist/credentials/SentryIoServerApi.credentials.js", "dist/credentials/SentryIoOAuth2Api.credentials.js", + "dist/credentials/SentryIoServerApi.credentials.js", "dist/credentials/ServiceNowOAuth2Api.credentials.js", + "dist/credentials/Sftp.credentials.js", "dist/credentials/ShopifyApi.credentials.js", "dist/credentials/Signl4Api.credentials.js", "dist/credentials/SlackApi.credentials.js", "dist/credentials/SlackOAuth2Api.credentials.js", "dist/credentials/Sms77Api.credentials.js", - "dist/credentials/Snowflake.credentials.js", "dist/credentials/Smtp.credentials.js", - "dist/credentials/SpotifyOAuth2Api.credentials.js", - "dist/credentials/StackbyApi.credentials.js", - "dist/credentials/StravaOAuth2Api.credentials.js", - "dist/credentials/StripeApi.credentials.js", - "dist/credentials/SalesmateApi.credentials.js", - "dist/credentials/SegmentApi.credentials.js", - "dist/credentials/SshPassword.credentials.js", - "dist/credentials/SshPrivateKey.credentials.js", - "dist/credentials/Sftp.credentials.js", - "dist/credentials/Signl4Api.credentials.js", + "dist/credentials/Snowflake.credentials.js", "dist/credentials/SplunkApi.credentials.js", "dist/credentials/SpontitApi.credentials.js", "dist/credentials/SpotifyOAuth2Api.credentials.js", + "dist/credentials/SpotifyOAuth2Api.credentials.js", + "dist/credentials/SshPassword.credentials.js", + "dist/credentials/SshPrivateKey.credentials.js", + "dist/credentials/StackbyApi.credentials.js", "dist/credentials/StoryblokContentApi.credentials.js", "dist/credentials/StoryblokManagementApi.credentials.js", "dist/credentials/StrapiApi.credentials.js", + "dist/credentials/StravaOAuth2Api.credentials.js", + "dist/credentials/StripeApi.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TaigaApi.credentials.js", @@ -275,16 +272,16 @@ "dist/credentials/TimescaleDb.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TodoistOAuth2Api.credentials.js", + "dist/credentials/TogglApi.credentials.js", "dist/credentials/TravisCiApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", + "dist/credentials/TwakeCloudApi.credentials.js", + "dist/credentials/TwakeServerApi.credentials.js", "dist/credentials/TwilioApi.credentials.js", "dist/credentials/TwistOAuth2Api.credentials.js", "dist/credentials/TwitterOAuth1Api.credentials.js", "dist/credentials/TypeformApi.credentials.js", "dist/credentials/TypeformOAuth2Api.credentials.js", - "dist/credentials/TogglApi.credentials.js", - "dist/credentials/TwakeCloudApi.credentials.js", - "dist/credentials/TwakeServerApi.credentials.js", "dist/credentials/UnleashedSoftwareApi.credentials.js", "dist/credentials/UpleadApi.credentials.js", "dist/credentials/UProcApi.credentials.js", @@ -301,6 +298,7 @@ "dist/credentials/WufooApi.credentials.js", "dist/credentials/XeroOAuth2Api.credentials.js", "dist/credentials/YourlsApi.credentials.js", + "dist/credentials/YouTubeOAuth2Api.credentials.js", "dist/credentials/ZendeskApi.credentials.js", "dist/credentials/ZendeskOAuth2Api.credentials.js", "dist/credentials/ZohoOAuth2Api.credentials.js", @@ -312,21 +310,23 @@ "dist/nodes/ActionNetwork/ActionNetwork.node.js", "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", + "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", + "dist/nodes/Affinity/Affinity.node.js", + "dist/nodes/Affinity/AffinityTrigger.node.js", "dist/nodes/AgileCrm/AgileCrm.node.js", "dist/nodes/Airtable/Airtable.node.js", "dist/nodes/Airtable/AirtableTrigger.node.js", - "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", "dist/nodes/Amqp/Amqp.node.js", "dist/nodes/Amqp/AmqpTrigger.node.js", + "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js", "dist/nodes/Asana/Asana.node.js", "dist/nodes/Asana/AsanaTrigger.node.js", - "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js", - "dist/nodes/Affinity/Affinity.node.js", - "dist/nodes/Affinity/AffinityTrigger.node.js", "dist/nodes/Automizy/Automizy.node.js", "dist/nodes/Autopilot/Autopilot.node.js", "dist/nodes/Autopilot/AutopilotTrigger.node.js", "dist/nodes/Aws/AwsLambda.node.js", + "dist/nodes/Aws/AwsSns.node.js", + "dist/nodes/Aws/AwsSnsTrigger.node.js", "dist/nodes/Aws/Comprehend/AwsComprehend.node.js", "dist/nodes/Aws/DynamoDB/AwsDynamoDB.node.js", "dist/nodes/Aws/Rekognition/AwsRekognition.node.js", @@ -335,8 +335,6 @@ "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", "dist/nodes/Bannerbear/Bannerbear.node.js", "dist/nodes/Baserow/Baserow.node.js", "dist/nodes/Beeminder/Beeminder.node.js", @@ -356,12 +354,12 @@ "dist/nodes/Clearbit/Clearbit.node.js", "dist/nodes/ClickUp/ClickUp.node.js", "dist/nodes/ClickUp/ClickUpTrigger.node.js", - "dist/nodes/Clockify/ClockifyTrigger.node.js", "dist/nodes/Clockify/Clockify.node.js", + "dist/nodes/Clockify/ClockifyTrigger.node.js", "dist/nodes/Cockpit/Cockpit.node.js", - "dist/nodes/Compression.node.js", "dist/nodes/Coda/Coda.node.js", "dist/nodes/CoinGecko/CoinGecko.node.js", + "dist/nodes/Compression/Compression.node.js", "dist/nodes/Contentful/Contentful.node.js", "dist/nodes/ConvertKit/ConvertKit.node.js", "dist/nodes/ConvertKit/ConvertKitTrigger.node.js", @@ -369,11 +367,11 @@ "dist/nodes/Copper/CopperTrigger.node.js", "dist/nodes/Cortex/Cortex.node.js", "dist/nodes/CrateDb/CrateDb.node.js", - "dist/nodes/Cron.node.js", - "dist/nodes/Crypto.node.js", + "dist/nodes/Cron/Cron.node.js", + "dist/nodes/Crypto/Crypto.node.js", "dist/nodes/CustomerIo/CustomerIo.node.js", "dist/nodes/CustomerIo/CustomerIoTrigger.node.js", - "dist/nodes/DateTime.node.js", + "dist/nodes/DateTime/DateTime.node.js", "dist/nodes/DeepL/DeepL.node.js", "dist/nodes/Demio/Demio.node.js", "dist/nodes/Discord/Discord.node.js", @@ -382,32 +380,32 @@ "dist/nodes/Drift/Drift.node.js", "dist/nodes/Dropbox/Dropbox.node.js", "dist/nodes/Dropcontact/Dropcontact.node.js", - "dist/nodes/EditImage.node.js", + "dist/nodes/EditImage/EditImage.node.js", "dist/nodes/Egoi/Egoi.node.js", - "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js", "dist/nodes/Elastic/Elasticsearch/Elasticsearch.node.js", - "dist/nodes/EmailReadImap.node.js", - "dist/nodes/EmailSend.node.js", + "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js", + "dist/nodes/EmailReadImap/EmailReadImap.node.js", + "dist/nodes/EmailSend/EmailSend.node.js", "dist/nodes/Emelia/Emelia.node.js", "dist/nodes/Emelia/EmeliaTrigger.node.js", - "dist/nodes/ErrorTrigger.node.js", "dist/nodes/ERPNext/ERPNext.node.js", + "dist/nodes/ErrorTrigger/ErrorTrigger.node.js", "dist/nodes/Eventbrite/EventbriteTrigger.node.js", - "dist/nodes/ExecuteCommand.node.js", - "dist/nodes/ExecuteWorkflow.node.js", + "dist/nodes/ExecuteCommand/ExecuteCommand.node.js", + "dist/nodes/ExecuteWorkflow/ExecuteWorkflow.node.js", "dist/nodes/Facebook/FacebookGraphApi.node.js", "dist/nodes/Facebook/FacebookTrigger.node.js", "dist/nodes/FileMaker/FileMaker.node.js", - "dist/nodes/Freshservice/Freshservice.node.js", - "dist/nodes/Ftp.node.js", - "dist/nodes/Freshdesk/Freshdesk.node.js", - "dist/nodes/FreshworksCrm/FreshworksCrm.node.js", - "dist/nodes/FormIo/FormIoTrigger.node.js", "dist/nodes/Flow/Flow.node.js", "dist/nodes/Flow/FlowTrigger.node.js", + "dist/nodes/FormIo/FormIoTrigger.node.js", "dist/nodes/Formstack/FormstackTrigger.node.js", - "dist/nodes/Function.node.js", - "dist/nodes/FunctionItem.node.js", + "dist/nodes/Freshdesk/Freshdesk.node.js", + "dist/nodes/Freshservice/Freshservice.node.js", + "dist/nodes/FreshworksCrm/FreshworksCrm.node.js", + "dist/nodes/Ftp/Ftp.node.js", + "dist/nodes/Function/Function.node.js", + "dist/nodes/FunctionItem/FunctionItem.node.js", "dist/nodes/GetResponse/GetResponse.node.js", "dist/nodes/GetResponse/GetResponseTrigger.node.js", "dist/nodes/Ghost/Ghost.node.js", @@ -416,9 +414,9 @@ "dist/nodes/Github/GithubTrigger.node.js", "dist/nodes/Gitlab/Gitlab.node.js", "dist/nodes/Gitlab/GitlabTrigger.node.js", + "dist/nodes/Google/Analytics/GoogleAnalytics.node.js", "dist/nodes/Google/BigQuery/GoogleBigQuery.node.js", "dist/nodes/Google/Books/GoogleBooks.node.js", - "dist/nodes/Google/Analytics/GoogleAnalytics.node.js", "dist/nodes/Google/Calendar/GoogleCalendar.node.js", "dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js", "dist/nodes/Google/Contacts/GoogleContacts.node.js", @@ -446,19 +444,19 @@ "dist/nodes/HelpScout/HelpScoutTrigger.node.js", "dist/nodes/HomeAssistant/HomeAssistant.node.js", "dist/nodes/HtmlExtract/HtmlExtract.node.js", - "dist/nodes/HttpRequest.node.js", + "dist/nodes/HttpRequest/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", "dist/nodes/Hubspot/HubspotTrigger.node.js", "dist/nodes/HumanticAI/HumanticAi.node.js", "dist/nodes/Hunter/Hunter.node.js", - "dist/nodes/ICalendar.node.js", - "dist/nodes/If.node.js", - "dist/nodes/Iterable/Iterable.node.js", + "dist/nodes/ICalendar/ICalendar.node.js", + "dist/nodes/If/If.node.js", "dist/nodes/Intercom/Intercom.node.js", - "dist/nodes/Interval.node.js", - "dist/nodes/ItemLists.node.js", + "dist/nodes/Interval/Interval.node.js", "dist/nodes/InvoiceNinja/InvoiceNinja.node.js", "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js", + "dist/nodes/ItemLists/ItemLists.node.js", + "dist/nodes/Iterable/Iterable.node.js", "dist/nodes/Jira/Jira.node.js", "dist/nodes/Jira/JiraTrigger.node.js", "dist/nodes/JotForm/JotFormTrigger.node.js", @@ -472,13 +470,13 @@ "dist/nodes/Line/Line.node.js", "dist/nodes/LingvaNex/LingvaNex.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", - "dist/nodes/LocalFileTrigger.node.js", + "dist/nodes/LocalFileTrigger/LocalFileTrigger.node.js", "dist/nodes/Magento/Magento2.node.js", - "dist/nodes/MailerLite/MailerLite.node.js", - "dist/nodes/MailerLite/MailerLiteTrigger.node.js", "dist/nodes/Mailcheck/Mailcheck.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailchimp/MailchimpTrigger.node.js", + "dist/nodes/MailerLite/MailerLite.node.js", + "dist/nodes/MailerLite/MailerLiteTrigger.node.js", "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mailjet/Mailjet.node.js", "dist/nodes/Mailjet/MailjetTrigger.node.js", @@ -489,7 +487,7 @@ "dist/nodes/Mautic/Mautic.node.js", "dist/nodes/Mautic/MauticTrigger.node.js", "dist/nodes/Medium/Medium.node.js", - "dist/nodes/Merge.node.js", + "dist/nodes/Merge/Merge.node.js", "dist/nodes/MessageBird/MessageBird.node.js", "dist/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.js", "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", @@ -500,30 +498,29 @@ "dist/nodes/Microsoft/ToDo/MicrosoftToDo.node.js", "dist/nodes/Mindee/Mindee.node.js", "dist/nodes/Misp/Misp.node.js", - "dist/nodes/MonicaCrm/MonicaCrm.node.js", - "dist/nodes/MoveBinaryData.node.js", "dist/nodes/Mocean/Mocean.node.js", "dist/nodes/MondayCom/MondayCom.node.js", "dist/nodes/MongoDb/MongoDb.node.js", + "dist/nodes/MonicaCrm/MonicaCrm.node.js", + "dist/nodes/MoveBinaryData/MoveBinaryData.node.js", "dist/nodes/MQTT/Mqtt.node.js", "dist/nodes/MQTT/MqttTrigger.node.js", - "dist/nodes/MoveBinaryData.node.js", "dist/nodes/Msg91/Msg91.node.js", "dist/nodes/MySql/MySql.node.js", - "dist/nodes/N8nTrigger.node.js", + "dist/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.node.js", + "dist/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.node.js", + "dist/nodes/N8nTrigger/N8nTrigger.node.js", "dist/nodes/Nasa/Nasa.node.js", "dist/nodes/Netlify/Netlify.node.js", "dist/nodes/Netlify/NetlifyTrigger.node.js", "dist/nodes/NextCloud/NextCloud.node.js", - "dist/nodes/NoOp.node.js", "dist/nodes/NocoDB/NocoDB.node.js", + "dist/nodes/NoOp/NoOp.node.js", "dist/nodes/Notion/Notion.node.js", "dist/nodes/Notion/NotionTrigger.node.js", - "dist/nodes/N8nTrainingCustomerDatastore.node.js", - "dist/nodes/N8nTrainingCustomerMessenger.node.js", "dist/nodes/OneSimpleApi/OneSimpleApi.node.js", "dist/nodes/OpenThesaurus/OpenThesaurus.node.js", - "dist/nodes/OpenWeatherMap.node.js", + "dist/nodes/OpenWeatherMap/OpenWeatherMap.node.js", "dist/nodes/Orbit/Orbit.node.js", "dist/nodes/Oura/Oura.node.js", "dist/nodes/Paddle/Paddle.node.js", @@ -532,9 +529,9 @@ "dist/nodes/PayPal/PayPalTrigger.node.js", "dist/nodes/Peekalink/Peekalink.node.js", "dist/nodes/Phantombuster/Phantombuster.node.js", + "dist/nodes/PhilipsHue/PhilipsHue.node.js", "dist/nodes/Pipedrive/Pipedrive.node.js", "dist/nodes/Pipedrive/PipedriveTrigger.node.js", - "dist/nodes/PhilipsHue/PhilipsHue.node.js", "dist/nodes/Plivo/Plivo.node.js", "dist/nodes/Postgres/Postgres.node.js", "dist/nodes/PostHog/PostHog.node.js", @@ -548,54 +545,54 @@ "dist/nodes/QuickBase/QuickBase.node.js", "dist/nodes/QuickBooks/QuickBooks.node.js", "dist/nodes/RabbitMQ/RabbitMQ.node.js", - "dist/nodes/Raindrop/Raindrop.node.js", "dist/nodes/RabbitMQ/RabbitMQTrigger.node.js", - "dist/nodes/ReadBinaryFile.node.js", - "dist/nodes/ReadBinaryFiles.node.js", - "dist/nodes/ReadPdf.node.js", + "dist/nodes/Raindrop/Raindrop.node.js", + "dist/nodes/ReadBinaryFile/ReadBinaryFile.node.js", + "dist/nodes/ReadBinaryFiles/ReadBinaryFiles.node.js", + "dist/nodes/ReadPdf/ReadPdf.node.js", "dist/nodes/Reddit/Reddit.node.js", "dist/nodes/Redis/Redis.node.js", - "dist/nodes/RenameKeys.node.js", - "dist/nodes/RespondToWebhook.node.js", + "dist/nodes/RenameKeys/RenameKeys.node.js", + "dist/nodes/RespondToWebhook/RespondToWebhook.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", - "dist/nodes/RssFeedRead.node.js", + "dist/nodes/RssFeedRead/RssFeedRead.node.js", "dist/nodes/Rundeck/Rundeck.node.js", "dist/nodes/S3/S3.node.js", "dist/nodes/Salesforce/Salesforce.node.js", + "dist/nodes/Salesmate/Salesmate.node.js", "dist/nodes/SeaTable/SeaTable.node.js", "dist/nodes/SeaTable/SeaTableTrigger.node.js", "dist/nodes/SecurityScorecard/SecurityScorecard.node.js", - "dist/nodes/Set.node.js", - "dist/nodes/SentryIo/SentryIo.node.js", + "dist/nodes/Segment/Segment.node.js", "dist/nodes/SendGrid/SendGrid.node.js", + "dist/nodes/Sendy/Sendy.node.js", + "dist/nodes/SentryIo/SentryIo.node.js", "dist/nodes/ServiceNow/ServiceNow.node.js", + "dist/nodes/Set/Set.node.js", "dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Signl4/Signl4.node.js", "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/Snowflake/Snowflake.node.js", - "dist/nodes/SplitInBatches.node.js", + "dist/nodes/SplitInBatches/SplitInBatches.node.js", "dist/nodes/Splunk/Splunk.node.js", "dist/nodes/Spontit/Spontit.node.js", "dist/nodes/Spotify/Spotify.node.js", - "dist/nodes/SpreadsheetFile.node.js", - "dist/nodes/Stackby/Stackby.node.js", - "dist/nodes/SseTrigger.node.js", + "dist/nodes/SpreadsheetFile/SpreadsheetFile.node.js", + "dist/nodes/SseTrigger/SseTrigger.node.js", "dist/nodes/Ssh/Ssh.node.js", - "dist/nodes/Start.node.js", + "dist/nodes/Stackby/Stackby.node.js", + "dist/nodes/Start/Start.node.js", + "dist/nodes/StopAndError/StopAndError.node.js", "dist/nodes/Storyblok/Storyblok.node.js", "dist/nodes/Strapi/Strapi.node.js", "dist/nodes/Strava/Strava.node.js", "dist/nodes/Strava/StravaTrigger.node.js", "dist/nodes/Stripe/Stripe.node.js", "dist/nodes/Stripe/StripeTrigger.node.js", - "dist/nodes/Switch.node.js", - "dist/nodes/Salesmate/Salesmate.node.js", - "dist/nodes/Segment/Segment.node.js", - "dist/nodes/Sendy/Sendy.node.js", - "dist/nodes/StopAndError.node.js", "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", + "dist/nodes/Switch/Switch.node.js", "dist/nodes/Taiga/Taiga.node.js", "dist/nodes/Taiga/TaigaTrigger.node.js", "dist/nodes/Tapfiliate/Tapfiliate.node.js", @@ -609,11 +606,11 @@ "dist/nodes/TravisCi/TravisCi.node.js", "dist/nodes/Trello/Trello.node.js", "dist/nodes/Trello/TrelloTrigger.node.js", + "dist/nodes/Twake/Twake.node.js", "dist/nodes/Twilio/Twilio.node.js", "dist/nodes/Twist/Twist.node.js", "dist/nodes/Twitter/Twitter.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js", - "dist/nodes/Twake/Twake.node.js", "dist/nodes/UnleashedSoftware/UnleashedSoftware.node.js", "dist/nodes/Uplead/Uplead.node.js", "dist/nodes/UProc/UProc.node.js", @@ -621,21 +618,21 @@ "dist/nodes/UrlScanIo/UrlScanIo.node.js", "dist/nodes/Vero/Vero.node.js", "dist/nodes/Vonage/Vonage.node.js", - "dist/nodes/Wait.node.js", + "dist/nodes/Wait/Wait.node.js", "dist/nodes/Webflow/Webflow.node.js", "dist/nodes/Webflow/WebflowTrigger.node.js", - "dist/nodes/Webhook.node.js", + "dist/nodes/Webhook/Webhook.node.js", "dist/nodes/Wekan/Wekan.node.js", - "dist/nodes/Wordpress/Wordpress.node.js", - "dist/nodes/WorkflowTrigger.node.js", - "dist/nodes/WooCommerce/WooCommerce.node.js", - "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", - "dist/nodes/WriteBinaryFile.node.js", - "dist/nodes/Wufoo/WufooTrigger.node.js", "dist/nodes/Wise/Wise.node.js", "dist/nodes/Wise/WiseTrigger.node.js", + "dist/nodes/WooCommerce/WooCommerce.node.js", + "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", + "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/WorkflowTrigger/WorkflowTrigger.node.js", + "dist/nodes/WriteBinaryFile/WriteBinaryFile.node.js", + "dist/nodes/Wufoo/WufooTrigger.node.js", "dist/nodes/Xero/Xero.node.js", - "dist/nodes/Xml.node.js", + "dist/nodes/Xml/Xml.node.js", "dist/nodes/Yourls/Yourls.node.js", "dist/nodes/Zendesk/Zendesk.node.js", "dist/nodes/Zendesk/ZendeskTrigger.node.js", From 57147910a91c3bc7789fec49f565da8226a54694 Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Thu, 18 Nov 2021 16:50:34 +0100 Subject: [PATCH 84/86] :bug: Fix One Simple API codex file (#2451) --- packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json index bd85e8ee3..ac863a7e8 100644 --- a/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json +++ b/packages/nodes-base/nodes/OneSimpleApi/OneSimpleApi.node.json @@ -8,7 +8,7 @@ "resources": { "credentialDocumentation": [ { - "url": "https://docs.n8n.io/credentials/OneSimpleAPI" + "url": "https://docs.n8n.io/credentials/oneSimpleApi/" } ], "primaryDocumentation": [ From 0c6af9fd952a0d380527441b587234da0a1c9266 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 19 Nov 2021 07:38:07 +0100 Subject: [PATCH 85/86] :zap: Pin @rudderstack/rudder-sdk-node to 1.0.6 in cli package to fix build issue --- 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 070ab83f9..8dc2ef1da 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -83,7 +83,7 @@ "dependencies": { "@oclif/command": "^1.5.18", "@oclif/errors": "^1.2.2", - "@rudderstack/rudder-sdk-node": "^1.0.2", + "@rudderstack/rudder-sdk-node": "1.0.6", "@types/json-diff": "^0.5.1", "@types/jsonwebtoken": "^8.5.2", "basic-auth": "^2.0.1", From d8598b01269f56de99557b33a14bbb0b4d6f8f4a Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Fri, 19 Nov 2021 10:17:13 +0100 Subject: [PATCH 86/86] :sparkles: Workflow canvas revamp (#2388) * bring back overrides * fix input output label positions * simply update label positions * refactor a bunch * update min x to show items * hide overlay on connection * only delete target connection, add maximum to push nodes out * rename const * rename const * set new insert position * fix insert behavior * update position handling * show arrow along with label * update connector * set endpoint styles * update pattern * push nodes up / down in case of if node * set position in switch * only one action at a time * add custom flow chart type * select start node by default when opening new workflow * add enter delay * fix delete bug * change connection type * add offset for if/switch/merge * fix gap * fix drag issue * implement new states * update disabled state * add selected state * make selects faster * update positioning * truncate when selected * remove offset for actions * fix icon scaling * refactor js plumb * fix looping behavior at close distance * lock version * change background to dots * update endpoints styling * increase spacing * udpate node z-index * fix output label positions * fix output label positions * reset location * add label offset * update border radius * fix height issue * fix parallaxing issue * fix zoomout issue * add success z-index * clean up js file * add package lock * fix z-index bug * update dot grid * update zoom level * set values, increase grid size * fix drop position * prevent duplicate connections * fix stub * use localstorage overrides for colors * add colors to system * revert no longer needed changes * revert no longer needed changes * add canvas colors * add canvas colors * use variable for id * force type * refactor helpers * add label constants * refactor func * refactor * fix * refactor * clean up css * refactor setzoom level * refactor * refactor * refactor func * remove scope * remove localstorage caching * clean up imports * update zero case * add delete connection * update selected state * add base type, remove straight line * add stub offset back * rename param * add label offset * update font size of items * move up label * fix error state while executing * disrespect stubs * check for errors * refactor position * clean up extra space * make entire node connectable * Revert "make entire node connectable" e304f7c5b8ff1b41268450c60ca4bc3b2ada5d4a * always show border * add border to zoom buttons * update spacing * update colors * allow connecting to entire node * fix pull conn active * two line names * apply select to all lines * increase input margin * override target pos * reset conn after pull * fix types * update orientation * fix up connectors snapping * hide arrow on pull * update overrides for connectors * change text * update pull colors * set to 1 line when selected * fix executions bug * build * refactor node component * remove comment * refactor more * remove prop * fix build issue * fix input drag bug in executions * reset offset * update select background * handle issue when endpoints are not set * fix connection aborted issue * add try catch to help show errors * wrap bind with try/catch * set default styles * reset pos despite zoom * add more checks * clean up impl * update icon * handle unknown types * hide items on init * fix importing unknown types with credentials * change opacity * push up item label * update color * update label class and colors * add to drop distance * fix z-index to match node * disable eslint * fix lasso tool selection * update background color * update waiting state * update tooltip positions * update wait node border * fix selection bug mostly * if selected, move above other nodes * add line through disabled nodes * remove node color option * move label above connection * success color for line through * update options index * hide waiting icon when disabled * fix gmail icon * refactor icons * clear execution data on disable/delete * fix selected node * fix executing behavior * optional __meta * set grid size * remove default color * remove node color * add comments * comments * add comments * remove empty space * update comment * refactor uuids * fix type issue * Revert "fix type issue" 9523b34f9604f75253ae0631f29fc27267a99d78 * Revert "fix type issue" 9523b34f9604f75253ae0631f29fc27267a99d78 * Revert "refactor uuids" 07f6848065cb9a98475fddb8330846106f9e70ad * fix build issues * refactor * update uuid * child nodes * skip nodes behind when pushing in loop * shift output icon for switch node * don't show output if waiting * waiting on init * build * change to bezier * revert connector change * add bezier type * fix snapping * clean up impl * refactor func * make const * rename type * refactor to simplify * Revert "refactor to simplify" 2db0ed504c752c33de975370d86a83a04ffcda14 * enable flowchart mode * clean up flowchart type * refactor type * merge types * configure curviness * set in localstorage * fix straight line arrow bug * show arrow when pulling * refactor / simplify * fix target gap in bezier * refactor target gap * add comments * add comment * fix dragging connections * fix bug when moving connection * update comment * rename file * update values * update minor * update straight line box * clean up conn types * clean up z-indexes * move color filters to node icon * update background color * update to use grid size value * fix endpoint offsets * set yspan range lower * remove overlays when moving conn * prevent unwanted connections * fix messed up connections * remove console log * clear execution issues on workflow run * update corner radius * fix drag/delete bug * increase offset * update disabled state * address comments * refactor * refactor func * :zap: Add full license text to N8nCustomConnectorType.js Co-authored-by: Jan Oberhauser --- .../src/styleguide/border.stories.mdx | 2 +- .../src/styleguide/colors.stories.mdx | 17 +- packages/design-system/theme/src/_tokens.scss | 46 + packages/editor-ui/src/Interface.ts | 69 +- packages/editor-ui/src/components/Node.vue | 344 +++-- .../src/components/NodeCredentials.vue | 2 +- .../editor-ui/src/components/NodeIcon.vue | 85 +- .../editor-ui/src/components/NodeSettings.vue | 28 +- .../editor-ui/src/components/NodeWebhooks.vue | 2 +- packages/editor-ui/src/components/RunData.vue | 5 +- packages/editor-ui/src/components/helpers.ts | 5 + .../src/components/mixins/mouseSelect.ts | 32 +- .../src/components/mixins/moveNodeWorkflow.ts | 28 +- .../src/components/mixins/nodeBase.ts | 237 +--- .../src/components/mixins/nodeHelpers.ts | 1 + .../src/components/mixins/showMessage.ts | 3 +- .../src/components/mixins/workflowHelpers.ts | 15 +- .../src/components/mixins/workflowRun.ts | 1 + packages/editor-ui/src/constants.ts | 1 + .../editor-ui/src/n8n-theme-variables.scss | 2 - packages/editor-ui/src/n8n-theme.scss | 3 +- .../src/plugins/N8nCustomConnectorType.js | 779 +++++++++++ packages/editor-ui/src/store.ts | 36 +- packages/editor-ui/src/views/NodeView.vue | 1229 +++++++++-------- packages/editor-ui/src/views/canvasHelpers.ts | 725 ++++++++++ packages/editor-ui/src/views/helpers.ts | 85 -- 26 files changed, 2720 insertions(+), 1062 deletions(-) create mode 100644 packages/editor-ui/src/plugins/N8nCustomConnectorType.js create mode 100644 packages/editor-ui/src/views/canvasHelpers.ts delete mode 100644 packages/editor-ui/src/views/helpers.ts diff --git a/packages/design-system/src/styleguide/border.stories.mdx b/packages/design-system/src/styleguide/border.stories.mdx index d5a83e064..b7f0785f3 100644 --- a/packages/design-system/src/styleguide/border.stories.mdx +++ b/packages/design-system/src/styleguide/border.stories.mdx @@ -16,7 +16,7 @@ import VariableTable from './VariableTable.vue'; {{ - template: ``, + template: ``, components: { VariableTable, }, diff --git a/packages/design-system/src/styleguide/colors.stories.mdx b/packages/design-system/src/styleguide/colors.stories.mdx index 16e372bcd..7090bbde3 100644 --- a/packages/design-system/src/styleguide/colors.stories.mdx +++ b/packages/design-system/src/styleguide/colors.stories.mdx @@ -44,7 +44,7 @@ import ColorCircles from './ColorCircles.vue'; {{ - template: ``, + template: ``, components: { ColorCircles, }, @@ -109,7 +109,7 @@ import ColorCircles from './ColorCircles.vue'; {{ - template: ``, + template: ``, components: { ColorCircles, }, @@ -129,3 +129,16 @@ import ColorCircles from './ColorCircles.vue'; }} + +## Canvas + + + + {{ + template: ``, + components: { + ColorCircles, + }, + }} + + diff --git a/packages/design-system/theme/src/_tokens.scss b/packages/design-system/theme/src/_tokens.scss index c2c836db9..24e245a34 100644 --- a/packages/design-system/theme/src/_tokens.scss +++ b/packages/design-system/theme/src/_tokens.scss @@ -75,6 +75,15 @@ var(--color-success-tint-2-l) ); + --color-success-light-h: 150; + --color-success-light-s: 54%; + --color-success-light-l: 70%; + --color-success-light: hsl( + var(--color-success-light-h), + var(--color-success-light-s), + var(--color-success-light-l) + ); + --color-warning-h: 36; --color-warning-s: 77%; --color-warning-l: 57%; @@ -187,6 +196,24 @@ var(--color-text-xlight-l) ); + --color-foreground-xdark-h: 220; + --color-foreground-xdark-s: 7.4%; + --color-foreground-xdark-l: 52.5%; + --color-foreground-xdark: hsl( + var(--color-foreground-xdark-h), + var(--color-foreground-xdark-s), + var(--color-foreground-xdark-l) + ); + + --color-foreground-dark-h: 228; + --color-foreground-dark-s: 9.6%; + --color-foreground-dark-l: 79.6%; + --color-foreground-dark: hsl( + var(--color-foreground-dark-h), + var(--color-foreground-dark-s), + var(--color-foreground-dark-l) + ); + --color-foreground-base-h: 220; --color-foreground-base-s: 20%; --color-foreground-base-l: 88.2%; @@ -259,6 +286,25 @@ var(--color-background-xlight-l) ); + --color-canvas-dot-h: 204; + --color-canvas-dot-s: 15.6%; + --color-canvas-dot-l: 87.5%; + --color-canvas-dot: hsl( + var(--color-canvas-dot-h), + var(--color-canvas-dot-s), + var(--color-canvas-dot-l) + ); + + --color-canvas-background-h: 260; + --color-canvas-background-s: 100%; + --color-canvas-background-l: 99.4%; + --color-canvas-background: hsl( + var(--color-canvas-background-h), + var(--color-canvas-background-s), + var(--color-canvas-background-l) + ); + + --border-radius-xlarge: 12px; --border-radius-large: 8px; --border-radius-base: 4px; --border-radius-small: 2px; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 02fe67ec4..7332f3548 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -22,32 +22,61 @@ import { WorkflowExecuteMode, } from 'n8n-workflow'; -import { - PaintStyle, -} from 'jsplumb'; - declare module 'jsplumb' { + interface PaintStyle { + stroke?: string; + fill?: string; + strokeWidth?: number; + outlineStroke?: string; + outlineWidth?: number; + } + interface Anchor { lastReturnValue: number[]; } interface Connection { + __meta?: { + sourceNodeName: string, + sourceOutputIndex: number, + targetNodeName: string, + targetOutputIndex: number, + }; + canvas?: HTMLElement; + connector?: { + setTargetEndpoint: (endpoint: Endpoint) => void; + resetTargetEndpoint: () => void; + bounds: { + minX: number; + maxX: number; + minY: number; + maxY: number; + } + }; + // bind(event: string, (connection: Connection): void;): void; // tslint:disable-line:no-any - bind(event: string, callback: Function): void; // tslint:disable-line:no-any + bind(event: string, callback: Function): void; removeOverlay(name: string): void; removeOverlays(): void; setParameter(name: string, value: any): void; // tslint:disable-line:no-any setPaintStyle(arg0: PaintStyle): void; addOverlay(arg0: any[]): void; // tslint:disable-line:no-any setConnector(arg0: any[]): void; // tslint:disable-line:no-any + getUuids(): [string, string]; } interface Endpoint { + __meta?: { + nodeName: string, + index: number, + }; getOverlay(name: string): any; // tslint:disable-line:no-any } interface Overlay { setVisible(visible: boolean): void; + setLocation(location: number): void; + canvas?: HTMLElement; } interface OnConnectionBindInfo { @@ -66,18 +95,14 @@ export interface IEndpointOptions { dragProxy?: any; // tslint:disable-line:no-any endpoint?: string; endpointStyle?: object; + endpointHoverStyle?: object; isSource?: boolean; isTarget?: boolean; maxConnections?: number; overlays?: any; // tslint:disable-line:no-any parameters?: any; // tslint:disable-line:no-any uuid?: string; -} - -export interface IConnectionsUi { - [key: string]: { - [key: string]: IEndpointOptions; - }; + enabled?: boolean; } export interface IUpdateInformation { @@ -95,20 +120,16 @@ export interface INodeUpdatePropertiesInformation { }; } -export type XYPositon = [number, number]; +export type XYPosition = [number, number]; export type MessageType = 'success' | 'warning' | 'info' | 'error'; export interface INodeUi extends INode { - position: XYPositon; + position: XYPosition; color?: string; notes?: string; issues?: INodeIssues; - _jsPlumb?: { - endpoints?: { - [key: string]: IEndpointOptions[]; - }; - }; + name: string; } export interface INodeTypesMaxCount { @@ -604,7 +625,7 @@ export interface IRootState { lastSelectedNodeOutputIndex: number | null; nodeIndex: Array; nodeTypes: INodeTypeDescription[]; - nodeViewOffsetPosition: XYPositon; + nodeViewOffsetPosition: XYPosition; nodeViewMoveInProgress: boolean; selectedNodes: INodeUi[]; sessionId: string; @@ -670,5 +691,13 @@ export interface IRestApiContext { export interface IZoomConfig { scale: number; - offset: XYPositon; + offset: XYPosition; } + +export interface IBounds { + minX: number; + minY: number; + maxX: number; + maxY: number; +} + diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index adabe3b3f..3f806432c 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -1,25 +1,35 @@