diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 7170dda59..977c53fef 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -6,25 +6,23 @@ n8n is a free and open [fair-code](http://faircode.io) licensed node based Workf n8n.io - Screenshot - ## Contents -- [Demo](#demo) -- [Available integrations](#available-integrations) -- [Documentation](#documentation) -- [Start n8n in Docker](#start-n8n-in-docker) -- [Start with tunnel](#start-with-tunnel) -- [Securing n8n](#securing-n8n) -- [Persist data](#persist-data) -- [Passing Sensitive Data via File](#passing-sensitive-data-via-file) -- [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance) -- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt) -- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) -- [Support](#support) -- [Jobs](#jobs) -- [Upgrading](#upgrading) -- [License](#license) - + - [Demo](#demo) + - [Available integrations](#available-integrations) + - [Documentation](#documentation) + - [Start n8n in Docker](#start-n8n-in-docker) + - [Start with tunnel](#start-with-tunnel) + - [Securing n8n](#securing-n8n) + - [Persist data](#persist-data) + - [Passing Sensitive Data via File](#passing-sensitive-data-via-file) + - [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance) + - [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt) + - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) + - [Support](#support) + - [Jobs](#jobs) + - [Upgrading](#upgrading) + - [License](#license) ## Demo @@ -49,9 +47,9 @@ Additional information and example workflows on the n8n.io website: [https://n8n ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - n8nio/n8n + --name n8n \ + -p 5678:5678 \ + n8nio/n8n ``` You can then access n8n by opening: @@ -71,14 +69,13 @@ To use it simply start n8n with `--tunnel` ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start --tunnel + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start --tunnel ``` - ## Securing n8n By default n8n can be accessed by everybody. This is OK if you have it only running @@ -93,7 +90,6 @@ N8N_BASIC_AUTH_USER= N8N_BASIC_AUTH_PASSWORD= ``` - ## Persist data The workflow data gets by default saved in an SQLite database in the user @@ -102,10 +98,10 @@ settings like webhook URL and encryption key. ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/root/.n8n \ + n8nio/n8n ``` ### Start with other Database @@ -121,7 +117,6 @@ for the credentials. If none gets found n8n creates automatically one on startup. In case credentials are already saved with a different encryption key it can not be used anymore as encrypting it is not possible anymore. - #### Use with MongoDB > **WARNING**: Use Postgres if possible! Mongo has problems with saving large @@ -129,40 +124,39 @@ it can not be used anymore as encrypting it is not possible anymore. > may be dropped in the future. Replace the following placeholders with the actual data: - - - - - - - - - - + - MONGO_DATABASE + - MONGO_HOST + - MONGO_PORT + - MONGO_USER + - MONGO_PASSWORD ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=mongodb \ -e DB_MONGODB_CONNECTION_URL="mongodb://:@:/" \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withMongo/README.md) - #### Use with PostgresDB Replace the following placeholders with the actual data: - - - - - - - - - - - - + - POSTGRES_DATABASE + - POSTGRES_HOST + - POSTGRES_PASSWORD + - POSTGRES_PORT + - POSTGRES_USER + - POSTGRES_SCHEMA ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=postgresdb \ -e DB_POSTGRESDB_DATABASE= \ -e DB_POSTGRESDB_HOST= \ @@ -170,39 +164,37 @@ docker run -it --rm \ -e DB_POSTGRESDB_USER= \ -e DB_POSTGRESDB_SCHEMA= \ -e DB_POSTGRESDB_PASSWORD= \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withPostgres/README.md) - #### Use with MySQL Replace the following placeholders with the actual data: - - - - - - - - - - + - MYSQLDB_DATABASE + - MYSQLDB_HOST + - MYSQLDB_PASSWORD + - MYSQLDB_PORT + - MYSQLDB_USER ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=mysqldb \ -e DB_MYSQLDB_DATABASE= \ -e DB_MYSQLDB_HOST= \ -e DB_MYSQLDB_PORT= \ -e DB_MYSQLDB_USER= \ -e DB_MYSQLDB_PASSWORD= \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` - ## Passing Sensitive Data via File To avoid passing sensitive information via environment variables "_FILE" may be @@ -211,16 +203,15 @@ with the given name. That makes it possible to load data easily from Docker- and Kubernetes-Secrets. The following environment variables support file input: - - DB_MONGODB_CONNECTION_URL_FILE - - DB_POSTGRESDB_DATABASE_FILE - - DB_POSTGRESDB_HOST_FILE - - DB_POSTGRESDB_PASSWORD_FILE - - DB_POSTGRESDB_PORT_FILE - - DB_POSTGRESDB_USER_FILE - - DB_POSTGRESDB_SCHEMA_FILE - - N8N_BASIC_AUTH_PASSWORD_FILE - - N8N_BASIC_AUTH_USER_FILE - + - DB_MONGODB_CONNECTION_URL_FILE + - DB_POSTGRESDB_DATABASE_FILE + - DB_POSTGRESDB_HOST_FILE + - DB_POSTGRESDB_PASSWORD_FILE + - DB_POSTGRESDB_PORT_FILE + - DB_POSTGRESDB_USER_FILE + - DB_POSTGRESDB_SCHEMA_FILE + - N8N_BASIC_AUTH_PASSWORD_FILE + - N8N_BASIC_AUTH_USER_FILE ## Example Setup with Lets Encrypt @@ -235,7 +226,7 @@ docker pull n8nio/n8n # Stop current setup sudo docker-compose stop # Delete it (will only delete the docker-containers, data is stored separately) -sudo docker-compose rm +sudo docker-compose rm # Then start it again sudo docker-compose up -d ``` @@ -251,11 +242,11 @@ the environment variable `TZ`. Example to use the same timezone for both: ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e GENERIC_TIMEZONE="Europe/Berlin" \ -e TZ="Europe/Berlin" \ - n8nio/n8n + n8nio/n8n ``` diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index b3729e4f6..42fbaa5b1 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -19,7 +19,7 @@
- + Results: {{ dataCount }} Results: @@ -248,7 +248,11 @@ export default mixins( return executionData.resultData.runData; }, maxDisplayItemsOptions (): number[] { - return [25, 50, 100, 250, 500, 1000, this.dataCount].filter(option => option <= this.dataCount); + const options = [25, 50, 100, 250, 500, 1000].filter(option => option <= this.dataCount); + if (!options.includes(this.dataCount)) { + options.push(this.dataCount); + } + return options; }, node (): INodeUi | null { return this.$store.getters.activeNode; diff --git a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts index 33bf215b1..069ff5fe5 100644 --- a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts @@ -48,6 +48,9 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio query.api_token = credentials.apiToken; const options: OptionsWithUri = { + headers: { + Accept: 'application/json', + }, method, qs: query, uri: `https://api.pipedrive.com/v1${endpoint}`, @@ -93,7 +96,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio if (error.response && error.response.body && error.response.body.error) { // Try to return the error prettier - let errorMessage = `Pipedrive error response [${error.statusCode}]: ${error.response.body.error}`; + let errorMessage = `Pipedrive error response [${error.statusCode}]: ${error.response.body.error.message}`; if (error.response.body.error_info) { errorMessage += ` - ${error.response.body.error_info}`; } @@ -124,7 +127,7 @@ export async function pipedriveApiRequestAllItems(this: IHookFunctions | IExecut if (query === undefined) { query = {}; } - query.limit = 500; + query.limit = 100; query.start = 0; const returnData: IDataObject[] = []; @@ -133,7 +136,12 @@ export async function pipedriveApiRequestAllItems(this: IHookFunctions | IExecut do { responseData = await pipedriveApiRequest.call(this, method, endpoint, body, query); - returnData.push.apply(returnData, responseData.data); + // the search path returns data diferently + if (responseData.data.items) { + returnData.push.apply(returnData, responseData.data.items); + } else { + returnData.push.apply(returnData, responseData.data); + } query.start = responseData.additionalData.pagination.next_start; } while ( diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index a5cd93ab9..13786ba94 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -25,7 +25,6 @@ interface CustomProperty { value: string; } - /** * Add the additional fields to the body * @@ -362,6 +361,11 @@ export class Pipedrive implements INodeType { value: 'getAll', description: 'Get data of all persons', }, + { + name: 'Search', + value: 'search', + description: 'Search all persons', + }, { name: 'Update', value: 'update', @@ -2021,6 +2025,7 @@ export class Pipedrive implements INodeType { show: { operation: [ 'getAll', + 'search', ], }, }, @@ -2035,6 +2040,7 @@ export class Pipedrive implements INodeType { show: { operation: [ 'getAll', + 'search', ], returnAll: [ false, @@ -2088,6 +2094,81 @@ export class Pipedrive implements INodeType { }, ], }, + + // ---------------------------------- + // person:search + // ---------------------------------- + { + displayName: 'Term', + name: 'term', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'person', + ], + }, + }, + default: '', + description: 'The search term to look for. Minimum 2 characters (or 1 if using exact_match).', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'person', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Exact Match', + name: 'exactMatch', + type: 'boolean', + default: false, + description: 'When enabled, only full exact matches against the given term are returned. It is not case sensitive.', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: 'A comma-separated string array. The fields to perform the search from. Defaults to all of them.', + }, + { + displayName: 'Include Fields', + name: 'includeFields', + type: 'string', + default: '', + description: 'Supports including optional fields in the results which are not provided by default.', + }, + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + default: '', + description: 'Will filter Deals by the provided Organization ID.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + default: false, + description: `Returns the data exactly in the way it got received from the API.`, + }, + ], + }, ], }; @@ -2526,6 +2607,39 @@ export class Pipedrive implements INodeType { endpoint = `/persons`; + } else if (operation === 'search') { + // ---------------------------------- + // persons:search + // ---------------------------------- + + requestMethod = 'GET'; + + qs.term = this.getNodeParameter('term', i) as string; + returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.fields) { + qs.fields = additionalFields.fields as string; + } + + if (additionalFields.exactMatch) { + qs.exact_match = additionalFields.exactMatch as boolean; + } + + if (additionalFields.organizationId) { + qs.organization_id = parseInt(additionalFields.organizationId as string, 10); + } + + if (additionalFields.includeFields) { + qs.include_fields = additionalFields.includeFields as string; + } + + endpoint = `/persons/search`; + } else if (operation === 'update') { // ---------------------------------- // person:update @@ -2562,7 +2676,9 @@ export class Pipedrive implements INodeType { let responseData; if (returnAll === true) { + responseData = await pipedriveApiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + } else { if (customProperties !== undefined) { @@ -2597,6 +2713,19 @@ export class Pipedrive implements INodeType { responseData.data = []; } + if (operation === 'search' && responseData.data && responseData.data.items) { + responseData.data = responseData.data.items; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (additionalFields.rawData !== true) { + responseData.data = responseData.data.map((item: { result_score: number, item: object }) => { + return { + result_score: item.result_score, + ...item.item, + }; + }); + } + } + if (Array.isArray(responseData.data)) { returnData.push.apply(returnData, responseData.data as IDataObject[]); } else {