From cb0cfaa7719e5d80821405c34b808ce45a8d9825 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 3 Dec 2019 16:48:17 -0500 Subject: [PATCH 1/4] done --- .../credentials/HubspotApi.credentials.ts | 17 ++ .../nodes/Hubspot/DealDescription.ts | 133 ++++++++++ .../nodes/Hubspot/GenericFunctions.ts | 78 ++++++ .../nodes-base/nodes/Hubspot/Hubspot.node.ts | 237 ++++++++++++++++++ packages/nodes-base/nodes/Hubspot/hubspot.png | Bin 0 -> 3534 bytes packages/nodes-base/package.json | 6 +- 6 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/HubspotApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Hubspot/DealDescription.ts create mode 100644 packages/nodes-base/nodes/Hubspot/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Hubspot/Hubspot.node.ts create mode 100644 packages/nodes-base/nodes/Hubspot/hubspot.png diff --git a/packages/nodes-base/credentials/HubspotApi.credentials.ts b/packages/nodes-base/credentials/HubspotApi.credentials.ts new file mode 100644 index 0000000000..0b149d9288 --- /dev/null +++ b/packages/nodes-base/credentials/HubspotApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HubspotApi implements ICredentialType { + name = 'hubspotApi'; + displayName = 'Hubspot API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Hubspot/DealDescription.ts b/packages/nodes-base/nodes/Hubspot/DealDescription.ts new file mode 100644 index 0000000000..d37b398962 --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/DealDescription.ts @@ -0,0 +1,133 @@ +import { INodeProperties } from "n8n-workflow"; + +export const dealOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'deal', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a deal', + }, + { + name: 'Get', + value: 'get', + description: 'Get a deal', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const dealFields = [ + +/* -------------------------------------------------------------------------- */ +/* deal:create */ +/* -------------------------------------------------------------------------- */ + + { + displayName: 'Stages', + name: 'stages', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getDealStages' + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + options: [], + description: 'The dealstage is required when creating a deal. See the CRM Pipelines API for details on managing pipelines and stages.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Deal Name', + name: 'dealName', + type: 'string', + default: '', + }, + { + displayName: 'Deal Stage', + name: 'dealStage', + type: 'string', + default: '', + }, + { + displayName: 'Pipeline', + name: 'pipeline', + type: 'string', + default: '', + }, + { + displayName: 'Close Date', + name: 'closeDate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Amount', + name: 'amount', + type: 'string', + default: '', + }, + { + displayName: 'Deal Type', + name: 'dealType', + type: 'string', + default: '', + }, + { + displayName: 'Associated Company', + name: 'associatedCompany', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod:'getCompanies' , + }, + default: [], + }, + { + displayName: 'Associated Vids', + name: 'associatedVids', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod:'getContacts' , + }, + default: [], + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts new file mode 100644 index 0000000000..8cc245ffd6 --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -0,0 +1,78 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; +import { response } from 'express'; + +export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('hubspotApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + query!.hapikey = credentials.apiKey as string; + const options: OptionsWithUri = { + method, + qs: query, + uri: uri || `https://api.hubapi.com${endpoint}`, + body, + json: true + }; + + try { + return await this.helpers.request!(options); + } catch (error) { + const errorMessage = error.response.body.message || error.response.body.Message; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + + + +/** + * Make an API request to paginated hubspot endpoint + * and return all results + */ +export async function hubspotApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.limit = 250; + query.count = 100; + + do { + responseData = await hubspotApiRequest.call(this, method, endpoint, body, query); + query.offset = responseData.offset; + query['vid-offset'] = responseData['vid-offset']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['has-more'] !== undefined && + responseData['has-more'] !== null && + responseData['has-more'] !== false + ); + return returnData; +} + + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = ''; + } + return result; +} diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts new file mode 100644 index 0000000000..d978d9038d --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -0,0 +1,237 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + hubspotApiRequest, + hubspotApiRequestAllItems, + validateJSON, + } from './GenericFunctions'; +import { + dealOperations, + dealFields, +} from '../Hubspot/DealDescription'; + +export class Hubspot implements INodeType { + description: INodeTypeDescription = { + displayName: 'Hubspot', + name: 'hubspot', + icon: 'file:hubspot.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Hubspot API', + defaults: { + name: 'Hubspot', + color: '#356ae6', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'hubspotApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Deal', + value: 'deal', + }, + ], + default: 'deal', + description: 'Resource to consume.', + }, + + // Deal + ...dealOperations, + ...dealFields, + ], + }; + + methods = { + loadOptions: { + // Get all the groups to display them to user so that he can + // select them easily + async getDealStages(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let stages; + try { + const endpoint = '/crm-pipelines/v1/pipelines/deals'; + stages = await hubspotApiRequest.call(this, 'GET', endpoint, {}); + stages = stages.results[0].stages; + console.log(stages) + } catch (err) { + throw new Error(`Hubspot Error: ${err}`); + } + for (const stage of stages) { + const stageName = stage.label; + const stageId = stage.stageId; + returnData.push({ + name: stageName, + value: stageId, + }); + } + return returnData; + }, + + // Get all the companies to display them to user so that he can + // select them easily + async getCompanies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let companies; + try { + const endpoint = '/companies/v2/companies/paged'; + companies = await hubspotApiRequestAllItems.call(this, 'results', 'GET', endpoint); + } catch (err) { + throw new Error(`Hubspot Error: ${err}`); + } + for (const company of companies) { + const companyName = company.properties.name.value; + const companyId = company.companyId; + returnData.push({ + name: companyName, + value: companyId, + }); + } + return returnData; + }, + + // Get all the companies to display them to user so that he can + // select them easily + async getContacts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let contacts; + try { + const endpoint = '/contacts/v1/lists/all/contacts/all'; + contacts = await hubspotApiRequestAllItems.call(this, 'contacts', 'GET', endpoint); + } catch (err) { + throw new Error(`Hubspot Error: ${err}`); + } + for (const contact of contacts) { + const contactName = `${contact.properties.firstname.value} ${contact.properties.lastname.value}` ; + const contactId = contact.vid; + returnData.push({ + name: contactName, + value: contactId, + }); + } + return returnData; + } + } + }; + + async execute(this: IExecuteFunctions): Promise { + // const items = this.getInputData(); + // const returnData: IDataObject[] = []; + // const length = items.length as unknown as number; + // let responseData; + // const qs: IDataObject = {}; + + // const resource = this.getNodeParameter('resource', 0) as string; + // const operation = this.getNodeParameter('operation', 0) as string; + + // for (let i = 0; i < length; i++) { + // if (resource === 'payout') { + // if (operation === 'create') { + // const body: IPaymentBatch = {}; + // const header: ISenderBatchHeader = {}; + // const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; + // const senderBatchId = this.getNodeParameter('senderBatchId', i) as string; + // const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + // header.sender_batch_id = senderBatchId; + // if (additionalFields.emailSubject) { + // header.email_subject = additionalFields.emailSubject as string; + // } + // if (additionalFields.emailMessage) { + // header.email_message = additionalFields.emailMessage as string; + // } + // if (additionalFields.note) { + // header.note = additionalFields.note as string; + // } + // body.sender_batch_header = header; + // if (!jsonActive) { + // const payoutItems: IItem[] = []; + // const itemsValues = (this.getNodeParameter('itemsUi', i) as IDataObject).itemsValues as IDataObject[]; + // if (itemsValues && itemsValues.length > 0) { + // itemsValues.forEach(o => { + // const payoutItem: IItem = {}; + // const amount: IAmount = {}; + // amount.currency = o.currency as string; + // amount.value = parseFloat(o.amount as string); + // payoutItem.amount = amount; + // payoutItem.note = o.note as string || ''; + // payoutItem.receiver = o.receiverValue as string; + // payoutItem.recipient_type = o.recipientType as RecipientType; + // payoutItem.recipient_wallet = o.recipientWallet as RecipientWallet; + // payoutItem.sender_item_id = o.senderItemId as string || ''; + // payoutItems.push(payoutItem); + // }); + // body.items = payoutItems; + // } else { + // throw new Error('You must have at least one item.'); + // } + // } else { + // const itemsJson = validateJSON(this.getNodeParameter('itemsJson', i) as string); + // body.items = itemsJson; + // } + // try { + // responseData = await payPalApiRequest.call(this, '/payments/payouts', 'POST', body); + // } catch (err) { + // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); + // } + // } + // if (operation === 'get') { + // const payoutBatchId = this.getNodeParameter('payoutBatchId', i) as string; + // const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + // try { + // if (returnAll === true) { + // responseData = await payPalApiRequestAllItems.call(this, 'items', `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + // } else { + // qs.page_size = this.getNodeParameter('limit', i) as number; + // responseData = await payPalApiRequest.call(this, `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + // responseData = responseData.items; + // } + // } catch (err) { + // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); + // } + // } + // } else if (resource === 'payoutItem') { + // if (operation === 'get') { + // const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; + // try { + // responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}`, 'GET', {}, qs); + // } catch (err) { + // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); + // } + // } + // if (operation === 'cancel') { + // const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; + // try { + // responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}/cancel`, 'POST', {}, qs); + // } catch (err) { + // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); + // } + // } + // } + // if (Array.isArray(responseData)) { + // returnData.push.apply(returnData, responseData as IDataObject[]); + // } else { + // returnData.push(responseData as IDataObject); + // } + // } + return [this.helpers.returnJsonArray({})]; + } +} diff --git a/packages/nodes-base/nodes/Hubspot/hubspot.png b/packages/nodes-base/nodes/Hubspot/hubspot.png new file mode 100644 index 0000000000000000000000000000000000000000..4b15a3edfceeee2ea02e3596fa2902b894c4dee1 GIT binary patch literal 3534 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 60f74c27c2..552385284a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -41,9 +41,10 @@ "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", + "dist/credentials/HubspotApi.credentials.js", "dist/credentials/IntercomApi.credentials.js", - "dist/credentials/Imap.credentials.js", - "dist/credentials/JiraSoftwareCloudApi.credentials.js", + "dist/credentials/Imap.credentials.js", + "dist/credentials/JiraSoftwareCloudApi.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", @@ -100,6 +101,7 @@ "dist/nodes/Google/GoogleSheets.node.js", "dist/nodes/GraphQL/GraphQL.node.js", "dist/nodes/HttpRequest.node.js", + "dist/nodes/Hubspot/Hubspot.node.js", "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", "dist/nodes/Intercom/Intercom.node.js", From 67297944dac04487f3108a9f8b28f9fefe31be8c Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 3 Dec 2019 22:21:52 -0500 Subject: [PATCH 2/4] done --- .../nodes/Hubspot/DealDescription.ts | 11 +- .../nodes-base/nodes/Hubspot/DealInterface.ts | 12 ++ .../nodes/Hubspot/GenericFunctions.ts | 1 - .../nodes-base/nodes/Hubspot/Hubspot.node.ts | 203 +++++++++--------- 4 files changed, 121 insertions(+), 106 deletions(-) create mode 100644 packages/nodes-base/nodes/Hubspot/DealInterface.ts diff --git a/packages/nodes-base/nodes/Hubspot/DealDescription.ts b/packages/nodes-base/nodes/Hubspot/DealDescription.ts index d37b398962..2b24ba9c9d 100644 --- a/packages/nodes-base/nodes/Hubspot/DealDescription.ts +++ b/packages/nodes-base/nodes/Hubspot/DealDescription.ts @@ -36,8 +36,8 @@ export const dealFields = [ /* -------------------------------------------------------------------------- */ { - displayName: 'Stages', - name: 'stages', + displayName: 'Stage', + name: 'stage', type: 'options', required: true, typeOptions: { @@ -107,8 +107,11 @@ export const dealFields = [ { displayName: 'Deal Type', name: 'dealType', - type: 'string', - default: '', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getDealTypes', + }, + default: [], }, { displayName: 'Associated Company', diff --git a/packages/nodes-base/nodes/Hubspot/DealInterface.ts b/packages/nodes-base/nodes/Hubspot/DealInterface.ts new file mode 100644 index 0000000000..5bc7ea47c3 --- /dev/null +++ b/packages/nodes-base/nodes/Hubspot/DealInterface.ts @@ -0,0 +1,12 @@ +import { IDataObject } from "n8n-workflow"; + + +export interface IAssociation { + associatedCompanyIds?: number[]; + associatedVids?: number[]; +} + +export interface IDeal { + association?: IAssociation; + properties?: IDataObject[]; +} diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index 8cc245ffd6..2bb224c776 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -10,7 +10,6 @@ import { import { IDataObject, } from 'n8n-workflow'; -import { response } from 'express'; export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('hubspotApi'); diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts index d978d9038d..3f7a69253c 100644 --- a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -18,6 +18,7 @@ import { dealOperations, dealFields, } from '../Hubspot/DealDescription'; +import { IDeal, IAssociation } from './DealInterface'; export class Hubspot implements INodeType { description: INodeTypeDescription = { @@ -72,7 +73,6 @@ export class Hubspot implements INodeType { const endpoint = '/crm-pipelines/v1/pipelines/deals'; stages = await hubspotApiRequest.call(this, 'GET', endpoint, {}); stages = stages.results[0].stages; - console.log(stages) } catch (err) { throw new Error(`Hubspot Error: ${err}`); } @@ -129,109 +129,110 @@ export class Hubspot implements INodeType { }); } return returnData; - } + }, + + // Get all the deal types to display them to user so that he can + // select them easily + async getDealTypes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let dealTypes; + try { + const endpoint = '/properties/v1/deals/properties/named/dealtype'; + dealTypes = await hubspotApiRequest.call(this, 'GET', endpoint); + } catch (err) { + throw new Error(`Hubspot Error: ${err}`); + } + for (const dealType of dealTypes.options) { + const dealTypeName = dealType.label ; + const dealTypeId = dealType.value; + returnData.push({ + name: dealTypeName, + value: dealTypeId, + }); + } + return returnData; + }, } }; async execute(this: IExecuteFunctions): Promise { - // const items = this.getInputData(); - // const returnData: IDataObject[] = []; - // const length = items.length as unknown as number; - // let responseData; - // const qs: IDataObject = {}; - - // const resource = this.getNodeParameter('resource', 0) as string; - // const operation = this.getNodeParameter('operation', 0) as string; - - // for (let i = 0; i < length; i++) { - // if (resource === 'payout') { - // if (operation === 'create') { - // const body: IPaymentBatch = {}; - // const header: ISenderBatchHeader = {}; - // const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; - // const senderBatchId = this.getNodeParameter('senderBatchId', i) as string; - // const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - // header.sender_batch_id = senderBatchId; - // if (additionalFields.emailSubject) { - // header.email_subject = additionalFields.emailSubject as string; - // } - // if (additionalFields.emailMessage) { - // header.email_message = additionalFields.emailMessage as string; - // } - // if (additionalFields.note) { - // header.note = additionalFields.note as string; - // } - // body.sender_batch_header = header; - // if (!jsonActive) { - // const payoutItems: IItem[] = []; - // const itemsValues = (this.getNodeParameter('itemsUi', i) as IDataObject).itemsValues as IDataObject[]; - // if (itemsValues && itemsValues.length > 0) { - // itemsValues.forEach(o => { - // const payoutItem: IItem = {}; - // const amount: IAmount = {}; - // amount.currency = o.currency as string; - // amount.value = parseFloat(o.amount as string); - // payoutItem.amount = amount; - // payoutItem.note = o.note as string || ''; - // payoutItem.receiver = o.receiverValue as string; - // payoutItem.recipient_type = o.recipientType as RecipientType; - // payoutItem.recipient_wallet = o.recipientWallet as RecipientWallet; - // payoutItem.sender_item_id = o.senderItemId as string || ''; - // payoutItems.push(payoutItem); - // }); - // body.items = payoutItems; - // } else { - // throw new Error('You must have at least one item.'); - // } - // } else { - // const itemsJson = validateJSON(this.getNodeParameter('itemsJson', i) as string); - // body.items = itemsJson; - // } - // try { - // responseData = await payPalApiRequest.call(this, '/payments/payouts', 'POST', body); - // } catch (err) { - // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - // } - // } - // if (operation === 'get') { - // const payoutBatchId = this.getNodeParameter('payoutBatchId', i) as string; - // const returnAll = this.getNodeParameter('returnAll', 0) as boolean; - // try { - // if (returnAll === true) { - // responseData = await payPalApiRequestAllItems.call(this, 'items', `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); - // } else { - // qs.page_size = this.getNodeParameter('limit', i) as number; - // responseData = await payPalApiRequest.call(this, `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); - // responseData = responseData.items; - // } - // } catch (err) { - // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - // } - // } - // } else if (resource === 'payoutItem') { - // if (operation === 'get') { - // const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; - // try { - // responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}`, 'GET', {}, qs); - // } catch (err) { - // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - // } - // } - // if (operation === 'cancel') { - // const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; - // try { - // responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}/cancel`, 'POST', {}, qs); - // } catch (err) { - // throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - // } - // } - // } - // if (Array.isArray(responseData)) { - // returnData.push.apply(returnData, responseData as IDataObject[]); - // } else { - // returnData.push(responseData as IDataObject); - // } - // } - return [this.helpers.returnJsonArray({})]; + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'deal') { + if (operation === 'create') { + const body: IDeal = {}; + const association: IAssociation = {}; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const stage = this.getNodeParameter('stage', i) as string; + if (stage) { + // @ts-ignore + body.properties.push({ + name: 'dealstage', + value: stage + }); + } + if (additionalFields.associatedCompany) { + association.associatedCompanyIds = additionalFields.associatedCompany as number[]; + } + if (additionalFields.associatedVids) { + association.associatedVids = additionalFields.associatedVids as number[]; + } + if (additionalFields.dealName) { + // @ts-ignore + body.properties.push({ + name: 'dealname', + value: additionalFields.dealName as string + }); + } + if (additionalFields.closeDate) { + // @ts-ignore + body.properties.push({ + name: 'closedate', + value: new Date(additionalFields.closeDate as string).getTime() + }); + } + if (additionalFields.amount) { + // @ts-ignore + body.properties.push({ + name: 'amount', + value: additionalFields.amount as string + }); + } + if (additionalFields.dealType) { + // @ts-ignore + body.properties.push({ + name: 'dealtype', + value: additionalFields.dealType as string + }); + } + if (additionalFields.pipeline) { + // @ts-ignore + body.properties.push({ + name: 'pipeline', + value: additionalFields.pipeline as string + }); + } + body.association = association; + try { + const endpoint = '/deals/v1/deal'; + responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body); + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; } } From c87da488f6037528a6b59087a9e4e62fd1055f3d Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 4 Dec 2019 12:21:02 -0500 Subject: [PATCH 3/4] :sparkles: hubspot node done --- .../nodes/Hubspot/DealDescription.ts | 357 +++++++++++++++++- .../nodes-base/nodes/Hubspot/DealInterface.ts | 2 +- .../nodes/Hubspot/GenericFunctions.ts | 3 +- .../nodes-base/nodes/Hubspot/Hubspot.node.ts | 133 ++++++- 4 files changed, 489 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/nodes/Hubspot/DealDescription.ts b/packages/nodes-base/nodes/Hubspot/DealDescription.ts index 2b24ba9c9d..f0f27fe82d 100644 --- a/packages/nodes-base/nodes/Hubspot/DealDescription.ts +++ b/packages/nodes-base/nodes/Hubspot/DealDescription.ts @@ -18,11 +18,36 @@ export const dealOperations = [ value: 'create', description: 'Create a deal', }, + { + name: 'Update', + value: 'update', + description: 'Update a deal', + }, { name: 'Get', value: 'get', description: 'Get a deal', }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all deals', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a deals', + }, + { + name: 'Recents Created', + value: 'getRecentsCreated', + description: 'Get recently created deals', + }, + { + name: 'Recents Modified', + value: 'getRecentsModified', + description: 'Get recently modified deals', + }, ], default: 'create', description: 'The operation to perform.', @@ -34,9 +59,8 @@ export const dealFields = [ /* -------------------------------------------------------------------------- */ /* deal:create */ /* -------------------------------------------------------------------------- */ - { - displayName: 'Stage', + displayName: 'Deal Stage', name: 'stage', type: 'options', required: true, @@ -133,4 +157,333 @@ export const dealFields = [ }, ] }, +/* -------------------------------------------------------------------------- */ +/* deal:update */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular deal', +}, +{ + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Update Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Deal Name', + name: 'dealName', + type: 'string', + default: '', + }, + { + displayName: 'Deal Stage', + name: 'stage', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getDealStages' + }, + default: [], + description: 'The dealstage is required when creating a deal. See the CRM Pipelines API for details on managing pipelines and stages.', + }, + { + displayName: 'Deal Stage', + name: 'dealStage', + type: 'string', + default: '', + }, + { + displayName: 'Pipeline', + name: 'pipeline', + type: 'string', + default: '', + }, + { + displayName: 'Close Date', + name: 'closeDate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Amount', + name: 'amount', + type: 'string', + default: '', + }, + { + displayName: 'Deal Type', + name: 'dealType', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getDealTypes', + }, + default: [], + }, + ] +}, +/* -------------------------------------------------------------------------- */ +/* deal:get */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular deal', +}, +{ + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + options: [ + { + displayName: 'Include Property Versions ', + name: 'includePropertyVersions', + type: 'boolean', + default: false, + description: `By default, you will only get data for the most recent version of a property in the "versions" data.
+ If you include this parameter, you will get data for all previous versions.`, + }, + ] +}, +/* -------------------------------------------------------------------------- */ +/* deal:getAll */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + 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: [ + 'deal', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 250, + }, + default: 100, + description: 'How many results to return.', +}, +{ + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Include Associations', + name: 'includeAssociations', + type: 'boolean', + default: false, + description: `Include the IDs of the associated contacts and companies in the results
. + This will also automatically include the num_associated_contacts property.`, + }, + { + displayName: 'Properties', + name: 'properties', + type: 'string', + default: '', + description: `Used to include specific deal properties in the results.
+ By default, the results will only include Deal ID and will not include the values for any properties for your Deals.
+ Including this parameter will include the data for the specified property in the results.
+ You can include this parameter multiple times to request multiple properties separed by ,.`, + }, + { + displayName: 'Properties With History', + name: 'propertiesWithHistory', + type: 'string', + default: '', + description: `Works similarly to properties=, but this parameter will include the history for the specified property,
+ instead of just including the current value. Use this parameter when you need the full history of changes to a property's value.`, + }, + ] +}, +/* -------------------------------------------------------------------------- */ +/* deal:delete */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Deal ID', + name: 'dealId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Unique identifier for a particular deal', +}, +/* -------------------------------------------------------------------------- */ +/* deal:getRecentsCreated deal:getRecentsModified */ +/* -------------------------------------------------------------------------- */ +{ + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getRecentsCreated', + 'getRecentsModified', + ], + }, + }, + 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: [ + 'deal', + ], + operation: [ + 'getRecentsCreated', + 'getRecentsModified', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 250, + }, + default: 100, + description: 'How many results to return.', +}, +{ + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getRecentsCreated', + 'getRecentsModified', + ], + }, + }, + options: [ + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: `Only return deals created after timestamp x`, + }, + { + displayName: 'Include Property Versions', + name: 'includePropertyVersions', + type: 'boolean', + default: false, + description: `By default, you will only get data for the most recent version of a property in the "versions" data.
+ If you include this parameter, you will get data for all previous versions.`, + }, + ] +}, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Hubspot/DealInterface.ts b/packages/nodes-base/nodes/Hubspot/DealInterface.ts index 5bc7ea47c3..92a41d6c95 100644 --- a/packages/nodes-base/nodes/Hubspot/DealInterface.ts +++ b/packages/nodes-base/nodes/Hubspot/DealInterface.ts @@ -7,6 +7,6 @@ export interface IAssociation { } export interface IDeal { - association?: IAssociation; + associations?: IAssociation; properties?: IDataObject[]; } diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index 2bb224c776..699a3d699c 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -22,7 +22,8 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions qs: query, uri: uri || `https://api.hubapi.com${endpoint}`, body, - json: true + json: true, + useQuerystring: true, }; try { diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts index 3f7a69253c..7ed33f7802 100644 --- a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -12,7 +12,6 @@ import { import { hubspotApiRequest, hubspotApiRequestAllItems, - validateJSON, } from './GenericFunctions'; import { dealOperations, @@ -167,6 +166,7 @@ export class Hubspot implements INodeType { if (resource === 'deal') { if (operation === 'create') { const body: IDeal = {}; + body.properties = []; const association: IAssociation = {}; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const stage = this.getNodeParameter('stage', i) as string; @@ -218,7 +218,7 @@ export class Hubspot implements INodeType { value: additionalFields.pipeline as string }); } - body.association = association; + body.associations = association; try { const endpoint = '/deals/v1/deal'; responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body); @@ -226,6 +226,135 @@ export class Hubspot implements INodeType { throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); } } + if (operation === 'update') { + const body: IDeal = {}; + body.properties = []; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const dealId = this.getNodeParameter('dealId', i) as string; + if (updateFields.stage) { + body.properties.push({ + name: 'dealstage', + value: updateFields.stage as string, + }); + } + if (updateFields.dealName) { + // @ts-ignore + body.properties.push({ + name: 'dealname', + value: updateFields.dealName as string + }); + } + if (updateFields.closeDate) { + // @ts-ignore + body.properties.push({ + name: 'closedate', + value: new Date(updateFields.closeDate as string).getTime() + }); + } + if (updateFields.amount) { + // @ts-ignore + body.properties.push({ + name: 'amount', + value: updateFields.amount as string + }); + } + if (updateFields.dealType) { + // @ts-ignore + body.properties.push({ + name: 'dealtype', + value: updateFields.dealType as string + }); + } + if (updateFields.pipeline) { + // @ts-ignore + body.properties.push({ + name: 'pipeline', + value: updateFields.pipeline as string + }); + } + try { + const endpoint = `/deals/v1/deal/${dealId}`; + responseData = await hubspotApiRequest.call(this, 'PUT', endpoint, body); + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'get') { + const dealId = this.getNodeParameter('dealId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (additionalFields.includePropertyVersions) { + qs.includePropertyVersions = additionalFields.includePropertyVersions as boolean; + } + try { + const endpoint = `/deals/v1/deal/${dealId}`; + responseData = await hubspotApiRequest.call(this, 'GET', endpoint); + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'getAll') { + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (filters.includeAssociations) { + qs.includeAssociations = filters.includeAssociations as boolean; + } + if (filters.properties) { + // @ts-ignore + qs.properties = filters.properties.split(','); + } + if (filters.propertiesWithHistory) { + // @ts-ignore + qs.propertiesWithHistory = filters.propertiesWithHistory.split(','); + } + try { + const endpoint = `/deals/v1/deal/paged`; + if (returnAll) { + responseData = await hubspotApiRequestAllItems.call(this, 'deals', 'GET', endpoint, {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await hubspotApiRequest.call(this, 'GET', endpoint, {}, qs); + responseData = responseData.deals; + } + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'getRecentsCreated' || operation === 'getRecentsModified') { + let endpoint; + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (filters.since) { + qs.since = new Date(filters.since as string).getTime(); + } + if (filters.includePropertyVersions) { + qs.includePropertyVersions = filters.includePropertyVersions as boolean; + } + try { + if (operation === 'getRecentsCreated') { + endpoint = `/deals/v1/deal/recent/created`; + } else { + endpoint = `/deals/v1/deal/recent/modified`; + } + if (returnAll) { + responseData = await hubspotApiRequestAllItems.call(this, 'results', 'GET', endpoint, {}, qs); + } else { + qs.count = this.getNodeParameter('limit', 0) as number; + responseData = await hubspotApiRequest.call(this, 'GET', endpoint, {}, qs); + responseData = responseData.results; + } + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'delete') { + const dealId = this.getNodeParameter('dealId', i) as string; + try { + const endpoint = `/deals/v1/deal/${dealId}`; + responseData = await hubspotApiRequest.call(this, 'DELETE', endpoint); + } catch (err) { + throw new Error(`Hubspot Error: ${JSON.stringify(err)}`); + } + } } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); From 6c1b479c81e95697859c1e36ba457389e826f6b0 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 4 Dec 2019 12:34:47 -0500 Subject: [PATCH 4/4] added hubspot API documentation --- packages/nodes-base/nodes/Hubspot/Hubspot.node.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts index 7ed33f7802..0abed92b50 100644 --- a/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts +++ b/packages/nodes-base/nodes/Hubspot/Hubspot.node.ts @@ -163,6 +163,7 @@ export class Hubspot implements INodeType { const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; for (let i = 0; i < length; i++) { + //https://developers.hubspot.com/docs/methods/deals/deals_overview if (resource === 'deal') { if (operation === 'create') { const body: IDeal = {};