diff --git a/packages/nodes-base/credentials/PipedriveApi.credentials.ts b/packages/nodes-base/credentials/PipedriveApi.credentials.ts new file mode 100644 index 000000000..f10dbef25 --- /dev/null +++ b/packages/nodes-base/credentials/PipedriveApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class PipedriveApi implements ICredentialType { + name = 'pipedriveApi'; + displayName = 'Pipedrive API'; + properties = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts new file mode 100644 index 000000000..ed291500c --- /dev/null +++ b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts @@ -0,0 +1,106 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to Pipedrive + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('pipedriveApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + if (query === undefined) { + query = {}; + } + + query.api_token = credentials.apiToken; + + const options = { + method, + body, + qs: query, + uri: `https://api.pipedrive.com/v1${endpoint}`, + json: true + }; + + try { + const responseData = await this.helpers.request(options); + + if (responseData.success === false) { + throw new Error(`Pipedrive error response: ${responseData.error} (${responseData.error_info})`); + } + + return { + additionalData: responseData.additional_data, + data: responseData.data, + }; + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Pipedrive credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error(`Pipedrive error response [${error.statusCode}]: ${error.response.body.message}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} + + + +/** + * Make an API request to paginated Pipedrive endpoint + * and return all results + * + * @export + * @param {(IHookFunctions | IExecuteFunctions)} this + * @param {string} method + * @param {string} endpoint + * @param {IDataObject} body + * @param {IDataObject} [query] + * @returns {Promise} + */ +export async function pipedriveApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any + + if (query === undefined) { + query = {}; + } + query.limit = 500; + query.start = 0; + + const returnData: IDataObject[] = []; + + let responseData; + + do { + responseData = await pipedriveApiRequest.call(this, method, endpoint, body, query); + returnData.push.apply(returnData, responseData.data); + + query.start = responseData.additionalData.pagination.next_start; + } while ( + responseData.additionalData !== undefined && + responseData.additionalData.pagination !== undefined && + responseData.additionalData.pagination.more_items_in_collection === true + ); + + return { + data: returnData + }; +} diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts new file mode 100644 index 000000000..52a7712da --- /dev/null +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -0,0 +1,1130 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; + +import { + pipedriveApiRequest, + pipedriveApiRequestAllItems, +} from './GenericFunctions'; + + +export class Pipedrive implements INodeType { + description: INodeTypeDescription = { + displayName: 'Pipedrive', + name: 'pipedrive', + icon: 'file:pipedrive.png', + group: ['transform'], + version: 1, + description: 'Access and edit Asana tasks', + defaults: { + name: 'Pipedrive', + color: '#227722', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'pipedriveApi', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Create Activity', + value: 'createActivity', + description: 'Creates an activity', + }, + { + name: 'Create Deal', + value: 'createDeal', + description: 'Creates a deal', + }, + { + name: 'Create Person', + value: 'createPerson', + description: 'Creates a person', + }, + { + name: 'Delete Activity', + value: 'deleteActivity', + description: 'Delete an activity', + }, + { + name: 'Delete Deal', + value: 'deleteDeal', + description: 'Delete a deal', + }, + { + name: 'Delete Person', + value: 'deletePerson', + description: 'Delete a person', + }, + { + name: 'Get Activity', + value: 'getActivity', + description: 'Get data of an activity', + }, + { + name: 'Get Deal', + value: 'getDeal', + description: 'Get data of a deal', + }, + { + name: 'Get All Persons', + value: 'getAllPersons', + description: 'Get data of all persons', + }, + { + name: 'Get Person', + value: 'getPerson', + description: 'Get data of a person', + }, + { + name: 'Update Activity', + value: 'updateActivity', + description: 'Update an activity', + }, + { + name: 'Update Deal', + value: 'updateDeal', + description: 'Update a deal', + }, + { + name: 'Update Person', + value: 'updatePerson', + description: 'Update a person', + }, + ], + default: 'createDeal', + description: 'The operation to perform.', + }, + + + // ---------------------------------- + // createActivity + // ---------------------------------- + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'createActivity', + ], + }, + }, + description: 'The subject of the activity to create', + }, + { + displayName: 'Done', + name: 'done', + type: 'options', + displayOptions: { + show: { + operation: [ + 'createActivity', + ], + }, + }, + options: [ + { + name: 'Not done', + value: '0', + }, + { + name: 'Done', + value: '1', + }, + ], + default: '0', + description: 'Whether the activity is done or not.', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'createActivity', + ], + }, + }, + placeholder: 'call', + description: 'Type of the activity like "call", "meeting", ...', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'createActivity', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Deal ID', + name: 'deal_id', + type: 'number', + default: 0, + description: 'ID of the deal this activity will be associated with', + }, + { + displayName: 'Note', + name: 'note', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + rows: 5, + }, + default: '', + description: 'Note of the activity (HTML format)', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this activity will be associated with', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person this activity will be associated with', + }, + { + displayName: 'User ID', + name: 'user_id', + type: 'number', + default: 0, + description: 'ID of the user whom the activity will be assigned to. If omitted, the activity will be assigned to the authorized user.', + }, + ], + }, + + + // ---------------------------------- + // createDeal + // ---------------------------------- + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'createDeal', + ], + }, + }, + description: 'The title of the deal to create', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'createDeal', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: 'USD', + description: 'Currency of the deal. Accepts a 3-character currency code. Like EUR, USD, ...', + }, + { + displayName: 'Lost Reason', + name: 'lost_reason', + type: 'string', + default: '', + description: 'Reason why the deal was lost.', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this deal will be associated with.', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person this deal will be associated with.', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 100, + }, + default: 0, + description: 'Deal success probability percentage.', + }, + { + displayName: 'Stage ID', + name: 'stage_id', + type: 'number', + default: 0, + description: 'ID of the stage this deal will be placed in a pipeline. If omitted, the deal will be placed in the first stage of the default pipeline.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Won', + value: 'won', + }, + { + name: 'Lost', + value: 'lost', + }, + { + name: 'Deleted', + value: 'deleted', + }, + ], + default: 'open', + description: 'The status of the deal. If not provided it will automatically be set to "open".', + }, + { + displayName: 'User ID', + name: 'user_id', + type: 'number', + default: 0, + description: 'ID of the user who will be marked as the owner of this deal. If omitted, the authorized user ID will be used.', + }, + { + displayName: 'Value', + name: 'value', + type: 'number', + default: 0, + description: 'Value of the deal. If not set it will automatically be set to 0.', + }, + { + displayName: 'Visible to', + name: 'visible_to', + type: 'options', + options: [ + { + name: 'Owner & followers (private)', + value: '1', + }, + { + name: 'Entire company (shared)', + value: '3', + }, + ], + default: '3', + description: 'Visibility of the deal. If omitted, visibility will be set to the default visibility setting of this item type for the authorized user.', + }, + ], + }, + + + // ---------------------------------- + // createPerson + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'createPerson', + ], + }, + }, + description: 'The name of the person to create', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'createPerson', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: '', + description: 'Email of the person.', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this person will belong to.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: '', + description: 'Phone number of the person.', + }, + { + displayName: 'Visible to', + name: 'visible_to', + type: 'options', + options: [ + { + name: 'Owner & followers (private)', + value: '1', + }, + { + name: 'Entire company (shared)', + value: '3', + }, + ], + default: '3', + description: 'Visibility of the deal. If omitted, visibility will be set to the default visibility setting of this item type for the authorized user.', + }, + ], + }, + + // ---------------------------------- + // deleteActivity + // ---------------------------------- + { + displayName: 'Activity ID', + name: 'activityId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'deleteActivity', + ], + }, + }, + default: 0, + required: true, + description: 'ID of the activity to delete.', + }, + + // ---------------------------------- + // deleteDeal + // ---------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'deleteDeal', + ], + }, + }, + default: 0, + required: true, + description: 'ID of the deal to delete.', + }, + + // ---------------------------------- + // deletePerson + // ---------------------------------- + { + displayName: 'Person ID', + name: 'personId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'deletePerson', + ], + }, + }, + default: 0, + required: true, + description: 'ID of the person to delete.', + }, + + + // ---------------------------------- + // getAllPersons + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAllPersons', + ], + }, + }, + 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: [ + 'getAllPersons', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + + // ---------------------------------- + // getActivity + // ---------------------------------- + { + displayName: 'Activity ID', + name: 'activityId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getActivity' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the activity to get.', + }, + + + // ---------------------------------- + // getDeal + // ---------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getDeal' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the deal to get.', + }, + + + // ---------------------------------- + // getPerson + // ---------------------------------- + { + displayName: 'Person ID', + name: 'personId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getPerson' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the person to get.', + }, + + + // ---------------------------------- + // updateActivity + // ---------------------------------- + { + displayName: 'Activity ID', + name: 'activityId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'updateActivity' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the activity to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'updateActivity' + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Deal ID', + name: 'deal_id', + type: 'number', + default: 0, + description: 'ID of the deal this activity will be associated with', + }, + { + displayName: 'Done', + name: 'done', + type: 'options', + options: [ + { + name: 'Not done', + value: '0', + }, + { + name: 'Done', + value: '1', + }, + ], + default: '0', + description: 'Whether the activity is done or not.', + }, + + { + displayName: 'Note', + name: 'note', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + rows: 5, + }, + default: '', + description: 'Note of the activity (HTML format)', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this activity will be associated with', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person this activity will be associated with', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + description: 'The subject of the activity', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + placeholder: 'call', + description: 'Type of the activity like "call", "meeting", ...', + }, + { + displayName: 'User ID', + name: 'user_id', + type: 'number', + default: 0, + description: 'ID of the user whom the activity will be assigned to. If omitted, the activity will be assigned to the authorized user.', + }, + ], + }, + + // ---------------------------------- + // updateDeal + // ---------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'updateDeal' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the deal to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'updateDeal' + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: 'USD', + description: 'Currency of the deal. Accepts a 3-character currency code. Like EUR, USD, ...', + }, + { + displayName: 'Lost Reason', + name: 'lost_reason', + type: 'string', + default: '', + description: 'Reason why the deal was lost.', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this deal will be associated with.', + }, + { + displayName: 'Person ID', + name: 'person_id', + type: 'number', + default: 0, + description: 'ID of the person this deal will be associated with.', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 100, + }, + default: 0, + description: 'Deal success probability percentage.', + }, + { + displayName: 'Stage ID', + name: 'stage_id', + type: 'number', + default: 0, + description: 'ID of the stage this deal will be placed in a pipeline. If omitted, the deal will be placed in the first stage of the default pipeline.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Won', + value: 'won', + }, + { + name: 'Lost', + value: 'lost', + }, + { + name: 'Deleted', + value: 'deleted', + }, + ], + default: 'open', + description: 'The status of the deal. If not provided it will automatically be set to "open".', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'The title of the deal', + }, + { + displayName: 'Value', + name: 'value', + type: 'number', + default: 0, + description: 'Value of the deal. If not set it will automatically be set to 0.', + }, + { + displayName: 'Visible to', + name: 'visible_to', + type: 'options', + options: [ + { + name: 'Owner & followers (private)', + value: '1', + }, + { + name: 'Entire company (shared)', + value: '3', + }, + ], + default: '3', + description: 'Visibility of the deal. If omitted, visibility will be set to the default visibility setting of this item type for the authorized user.', + }, + ], + }, + + + // ---------------------------------- + // updatePerson + // ---------------------------------- + { + displayName: 'Person ID', + name: 'personId', + type: 'number', + displayOptions: { + show: { + operation: [ + 'updatePerson' + ], + }, + }, + default: 0, + required: true, + description: 'ID of the person to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'updatePerson' + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: '', + description: 'Email of the person.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the person', + }, + { + displayName: 'Organization ID', + name: 'org_id', + type: 'number', + default: 0, + description: 'ID of the organization this person will belong to.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: '', + description: 'Phone number of the person.', + }, + { + displayName: 'Visible to', + name: 'visible_to', + type: 'options', + options: [ + { + name: 'Owner & followers (private)', + value: '1', + }, + { + name: 'Entire company (shared)', + value: '3', + }, + ], + default: '3', + description: 'Visibility of the deal. If omitted, visibility will be set to the default visibility setting of this item type for the authorized user.', + }, + ], + }, + + ], + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const operation = this.getNodeParameter('operation', 0) as string; + + // For Post + let body: IDataObject; + // For Query string + let qs: IDataObject; + + let requestMethod: string; + let endpoint: string; + let returnAll = false; + + for (let i = 0; i < items.length; i++) { + requestMethod = 'GET'; + endpoint = ''; + body = {}; + qs = {}; + + if (operation === 'createActivity') { + // ---------------------------------- + // createActivity + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = '/activities'; + + body.subject = this.getNodeParameter('subject', i) as string; + body.done = this.getNodeParameter('done', i) as string; + body.type = this.getNodeParameter('type', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(body, additionalFields); + + } else if (operation === 'createDeal') { + // ---------------------------------- + // createTask + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = '/deals'; + + body.title = this.getNodeParameter('title', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(body, additionalFields); + + } else if (operation === 'createPerson') { + // ---------------------------------- + // createPerson + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = '/persons'; + + body.name = this.getNodeParameter('name', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(body, additionalFields); + + } else if (operation === 'deleteActivity') { + // ---------------------------------- + // deleteActivity + // ---------------------------------- + + requestMethod = 'DELETE'; + + const activityId = this.getNodeParameter('activityId', i) as number; + endpoint = `/activities/${activityId}`; + + } else if (operation === 'deleteDeal') { + // ---------------------------------- + // deleteDeal + // ---------------------------------- + + requestMethod = 'DELETE'; + + const dealId = this.getNodeParameter('dealId', i) as number; + endpoint = `/deals/${dealId}`; + + } else if (operation === 'deletePerson') { + // ---------------------------------- + // deletePerson + // ---------------------------------- + + requestMethod = 'DELETE'; + + const personId = this.getNodeParameter('personId', i) as number; + endpoint = `/persons/${personId}`; + + } else if (operation === 'getAllPersons') { + // ---------------------------------- + // getAllPersons + // ---------------------------------- + + requestMethod = 'GET'; + + returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + endpoint = `/persons`; + + } else if (operation === 'getActivity') { + // ---------------------------------- + // getActivity + // ---------------------------------- + + requestMethod = 'GET'; + + const activityId = this.getNodeParameter('activityId', i) as number; + endpoint = `/activities/${activityId}`; + + } else if (operation === 'getDeal') { + // ---------------------------------- + // getDeal + // ---------------------------------- + + requestMethod = 'GET'; + + const dealId = this.getNodeParameter('dealId', i) as number; + endpoint = `/deals/${dealId}`; + + } else if (operation === 'getPerson') { + // ---------------------------------- + // getPerson + // ---------------------------------- + + requestMethod = 'GET'; + + const personId = this.getNodeParameter('personId', i) as number; + endpoint = `/persons/${personId}`; + + } else if (operation === 'updateActivity') { + // ---------------------------------- + // updateActivity + // ---------------------------------- + + requestMethod = 'PUT'; + + const activityId = this.getNodeParameter('activityId', i) as number; + endpoint = `/activities/${activityId}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(body, updateFields); + + } else if (operation === 'updateDeal') { + // ---------------------------------- + // updateDeal + // ---------------------------------- + + requestMethod = 'PUT'; + + const dealId = this.getNodeParameter('dealId', i) as number; + endpoint = `/deals/${dealId}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(body, updateFields); + + } else if (operation === 'updatePerson') { + // ---------------------------------- + // updatePerson + // ---------------------------------- + + requestMethod = 'PUT'; + + const personId = this.getNodeParameter('personId', i) as number; + endpoint = `/persons/${personId}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(body, updateFields); + + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + + let responseData; + if (returnAll === true) { + responseData = await pipedriveApiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + } else { + responseData = await pipedriveApiRequest.call(this, requestMethod, endpoint, body, qs); + } + + if (Array.isArray(responseData.data)) { + returnData.push.apply(returnData, responseData.data as IDataObject[]); + } else { + returnData.push(responseData.data as IDataObject); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Pipedrive/pipedrive.png b/packages/nodes-base/nodes/Pipedrive/pipedrive.png new file mode 100644 index 000000000..49f01df59 Binary files /dev/null and b/packages/nodes-base/nodes/Pipedrive/pipedrive.png differ