mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
🔀 Merge branch 'feature/extend-hubspot' of https://github.com/RicardoE105/n8n into RicardoE105-feature/extend-hubspot
This commit is contained in:
commit
7be0a0a7b3
326
packages/nodes-base/nodes/Hubspot/FormDescription.ts
Normal file
326
packages/nodes-base/nodes/Hubspot/FormDescription.ts
Normal file
|
@ -0,0 +1,326 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
|
||||
export const formOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Fields',
|
||||
value: 'getFields',
|
||||
description: 'Get all fields from a form',
|
||||
},
|
||||
{
|
||||
name: 'Submit',
|
||||
value: 'submit',
|
||||
description: 'Submit data to a form',
|
||||
},
|
||||
],
|
||||
default: 'getFields',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const formFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:submit */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getForms',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `The ID of the form you're sending data to.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Skip Validation',
|
||||
name: 'skipValidation',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to skip validation based on the form settings.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Submitted At',
|
||||
name: 'submittedAt',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Time of the form submission.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Context',
|
||||
name: 'contextUi',
|
||||
placeholder: 'Add Context',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Context',
|
||||
name: 'contextValue',
|
||||
values: [
|
||||
{
|
||||
displayName: 'HubSpot usertoken',
|
||||
name: 'hutk',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Include this parameter and set it to the hubspotutk cookie value to enable cookie tracking on your submission',
|
||||
},
|
||||
{
|
||||
displayName: 'IP Address',
|
||||
name: 'ipAddress',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The IP address of the visitor filling out the form.',
|
||||
},
|
||||
{
|
||||
displayName: 'Page URI',
|
||||
name: 'pageUri',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The URI of the page the submission happened on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Page Name',
|
||||
name: 'pageName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The name or title of the page the submission happened on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Page ID',
|
||||
name: 'pageId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The ID of a page created on the HubSpot CMS.',
|
||||
},
|
||||
{
|
||||
displayName: 'SFDC campaign ID',
|
||||
name: 'sfdcCampaignId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `If the form is for an account using the HubSpot Salesforce Integration,</br>
|
||||
you can include the ID of a Salesforce campaign to add the contact to the specified campaign.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Go to Webinar Webinar ID',
|
||||
name: 'goToWebinarWebinarKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `If the form is for an account using the HubSpot GoToWebinar Integration,</br>
|
||||
you can include the ID of a webinar to enroll the contact in that webinar when they submit the form.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Legal Consent',
|
||||
name: 'lengalConsentUi',
|
||||
placeholder: 'Add Legal Consent',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Consent',
|
||||
name: 'lengalConsentValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Consent To Process',
|
||||
name: 'consentToProcess',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether or not the visitor checked the Consent to process checkbox',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The text displayed to the visitor for the Consent to process checkbox',
|
||||
},
|
||||
{
|
||||
displayName: 'Communications',
|
||||
name: 'communicationsUi',
|
||||
placeholder: 'Add Communication',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Communication',
|
||||
name: 'communicationValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Subcription Type',
|
||||
name: 'subscriptionTypeId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSubscriptionTypes',
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the specific subscription type',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: ' Whether or not the visitor checked the checkbox for this subscription type.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The text displayed to the visitor for this specific subscription checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Legitimate Interest',
|
||||
name: 'legitimateInterestValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Subcription Type',
|
||||
name: 'subscriptionTypeId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSubscriptionTypes',
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the specific subscription type that this forms indicates interest to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `This must be true when using the 'legitimateInterest' option, as it reflects</br>
|
||||
the consent indicated by the visitor when submitting the form`,
|
||||
},
|
||||
{
|
||||
displayName: 'Legal Basis',
|
||||
name: 'legalBasis',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Customer',
|
||||
value: 'CUSTOMER',
|
||||
},
|
||||
{
|
||||
name: 'Lead',
|
||||
value: 'LEAD',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The privacy text displayed to the visitor.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The privacy text displayed to the visitor.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:getFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getForms',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getFields',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the form',
|
||||
},
|
||||
] as INodeProperties[];
|
21
packages/nodes-base/nodes/Hubspot/FormInterface.ts
Normal file
21
packages/nodes-base/nodes/Hubspot/FormInterface.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { IDataObject } from "n8n-workflow";
|
||||
|
||||
export interface IContext {
|
||||
goToWebinarWebinarKey?: string;
|
||||
hutk?: string;
|
||||
ipAddress?: string;
|
||||
pageId?: string;
|
||||
pageName?: string;
|
||||
pageUri?: string;
|
||||
sfdcCampaignId?: string;
|
||||
}
|
||||
|
||||
export interface IForm {
|
||||
portalId?: number;
|
||||
formId?: string;
|
||||
fields?: IDataObject[];
|
||||
legalConsentOptions?: IDataObject;
|
||||
context?: IContext[];
|
||||
submittedAt?: number;
|
||||
skipValidation?: boolean;
|
||||
}
|
|
@ -26,17 +26,20 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
json: true,
|
||||
useQuerystring: true,
|
||||
};
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = error.response.body.message || error.response.body.Message || error.message;
|
||||
throw new Error(`Hubspot error response [${error.statusCode}]: ${errorMessage}`);
|
||||
|
||||
if (error.response && error.response.body && error.response.body.errors) {
|
||||
// Try to return the error prettier
|
||||
const errorMessages = error.response.body.errors.map((e: IDataObject) => e.message);
|
||||
throw new Error(`Hubspot error response [${error.statusCode}]: ${errorMessages.join(' | ')}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Make an API request to paginated hubspot endpoint
|
||||
* and return all results
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
|
@ -9,15 +10,30 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
hubspotApiRequest,
|
||||
hubspotApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
dealOperations,
|
||||
dealFields,
|
||||
} from '../Hubspot/DealDescription';
|
||||
import { IDeal, IAssociation } from './DealInterface';
|
||||
} from './DealDescription';
|
||||
|
||||
import {
|
||||
IDeal,
|
||||
IAssociation
|
||||
} from './DealInterface';
|
||||
|
||||
import {
|
||||
formOperations,
|
||||
formFields,
|
||||
} from './FormDescription';
|
||||
|
||||
import {
|
||||
IForm
|
||||
} from './FormInterface';
|
||||
|
||||
export class Hubspot implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -50,6 +66,10 @@ export class Hubspot implements INodeType {
|
|||
name: 'Deal',
|
||||
value: 'deal',
|
||||
},
|
||||
{
|
||||
name: 'Form',
|
||||
value: 'form',
|
||||
},
|
||||
],
|
||||
default: 'deal',
|
||||
description: 'Resource to consume.',
|
||||
|
@ -58,23 +78,22 @@ export class Hubspot implements INodeType {
|
|||
// Deal
|
||||
...dealOperations,
|
||||
...dealFields,
|
||||
// Form
|
||||
...formOperations,
|
||||
...formFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
|
||||
// Get all the groups to display them to user so that he can
|
||||
// select them easily
|
||||
async getDealStages(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
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;
|
||||
} catch (err) {
|
||||
throw new Error(`Hubspot Error: ${err}`);
|
||||
}
|
||||
const endpoint = '/crm-pipelines/v1/pipelines/deals';
|
||||
let stages = await hubspotApiRequest.call(this, 'GET', endpoint, {});
|
||||
stages = stages.results[0].stages;
|
||||
for (const stage of stages) {
|
||||
const stageName = stage.label;
|
||||
const stageId = stage.stageId;
|
||||
|
@ -90,13 +109,8 @@ export class Hubspot implements INodeType {
|
|||
// select them easily
|
||||
async getCompanies(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
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}`);
|
||||
}
|
||||
const endpoint = '/companies/v2/companies/paged';
|
||||
const companies = await hubspotApiRequestAllItems.call(this, 'results', 'GET', endpoint);
|
||||
for (const company of companies) {
|
||||
const companyName = company.properties.name.value;
|
||||
const companyId = company.companyId;
|
||||
|
@ -112,13 +126,8 @@ export class Hubspot implements INodeType {
|
|||
// select them easily
|
||||
async getContacts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
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}`);
|
||||
}
|
||||
const endpoint = '/contacts/v1/lists/all/contacts/all';
|
||||
const contacts = await hubspotApiRequestAllItems.call(this, 'contacts', 'GET', endpoint);
|
||||
for (const contact of contacts) {
|
||||
const contactName = `${contact.properties.firstname.value} ${contact.properties.lastname.value}` ;
|
||||
const contactId = contact.vid;
|
||||
|
@ -134,13 +143,8 @@ export class Hubspot implements INodeType {
|
|||
// select them easily
|
||||
async getDealTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
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}`);
|
||||
}
|
||||
const endpoint = '/properties/v1/deals/properties/named/dealtype';
|
||||
const dealTypes = await hubspotApiRequest.call(this, 'GET', endpoint);
|
||||
for (const dealType of dealTypes.options) {
|
||||
const dealTypeName = dealType.label ;
|
||||
const dealTypeId = dealType.value;
|
||||
|
@ -151,6 +155,40 @@ export class Hubspot implements INodeType {
|
|||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the forms to display them to user so that he can
|
||||
// select them easily
|
||||
async getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const endpoint = '/forms/v2/forms';
|
||||
const forms = await hubspotApiRequest.call(this, 'GET', endpoint, {}, { formTypes: 'ALL' });
|
||||
for (const form of forms) {
|
||||
const formName = form.name;
|
||||
const formId = form.guid;
|
||||
returnData.push({
|
||||
name: formName,
|
||||
value: formId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the subscription types to display them to user so that he can
|
||||
// select them easily
|
||||
async getSubscriptionTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const endpoint = '/email/public/v1/subscriptions';
|
||||
const subscriptions = await hubspotApiRequestAllItems.call(this, 'subscriptionDefinitions', 'GET', endpoint, {});
|
||||
for (const subscription of subscriptions) {
|
||||
const subscriptionName = subscription.name;
|
||||
const subscriptionId = subscription.id;
|
||||
returnData.push({
|
||||
name: subscriptionName,
|
||||
value: subscriptionId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -357,6 +395,60 @@ export class Hubspot implements INodeType {
|
|||
}
|
||||
}
|
||||
}
|
||||
//https://developers.hubspot.com/docs/methods/forms/forms_overview
|
||||
if (resource === 'form') {
|
||||
//https://developers.hubspot.com/docs/methods/forms/v2/get_fields
|
||||
if (operation === 'getFields') {
|
||||
const formId = this.getNodeParameter('formId', i) as string;
|
||||
responseData = await hubspotApiRequest.call(this, 'GET', `/forms/v2/fields/${formId}`);
|
||||
}
|
||||
//https://developers.hubspot.com/docs/methods/forms/submit_form_v3
|
||||
if (operation === 'submit') {
|
||||
const formId = this.getNodeParameter('formId', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const context = (this.getNodeParameter('contextUi', i) as IDataObject).contextValue as IDataObject;
|
||||
const legalConsent = (this.getNodeParameter('lengalConsentUi', i) as IDataObject).lengalConsentValues as IDataObject;
|
||||
const legitimateInteres = (this.getNodeParameter('lengalConsentUi', i) as IDataObject).legitimateInterestValues as IDataObject;
|
||||
const { portalId } = await hubspotApiRequest.call(this, 'GET', `/forms/v2/forms/${formId}`);
|
||||
const body: IForm = {
|
||||
formId,
|
||||
portalId,
|
||||
legalConsentOptions: {},
|
||||
fields: [],
|
||||
};
|
||||
if (additionalFields.submittedAt) {
|
||||
body.submittedAt = new Date(additionalFields.submittedAt as string).getTime();
|
||||
}
|
||||
if (additionalFields.skipValidation) {
|
||||
body.skipValidation = additionalFields.skipValidation as boolean;
|
||||
}
|
||||
const consent: IDataObject = {};
|
||||
if (legalConsent) {
|
||||
if (legalConsent.consentToProcess) {
|
||||
consent!.consentToProcess = legalConsent.consentToProcess as boolean;
|
||||
}
|
||||
if (legalConsent.text) {
|
||||
consent!.text = legalConsent.text as string;
|
||||
}
|
||||
if (legalConsent.communicationsUi) {
|
||||
consent.communications = (legalConsent.communicationsUi as IDataObject).communicationValues as IDataObject;
|
||||
}
|
||||
}
|
||||
body.legalConsentOptions!.consent = consent;
|
||||
const fields: IDataObject = items[i].json;
|
||||
for (const key of Object.keys(fields)) {
|
||||
body.fields?.push({ name: key, value: fields[key] });
|
||||
}
|
||||
if (body.legalConsentOptions!.legitimateInterest) {
|
||||
Object.assign(body, { legalConsentOptions: { legitimateInterest: legitimateInteres } });
|
||||
}
|
||||
if (context) {
|
||||
Object.assign(body, { context });
|
||||
}
|
||||
const uri = `https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formId}`;
|
||||
responseData = await hubspotApiRequest.call(this, 'POST', '', body, {}, uri);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue