mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
✨ Add support for multiple subscriptions in Hubspot Trigger (#1358)
* ⚡ Add support for multiple subscriptions in Hubspot Trigger * ⚡ Load object properties for the user * ⚡ Improvements * ⚡ Some improvements to the Hubspot-Node Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
5549550928
commit
dc98de1ab2
|
@ -2,6 +2,18 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.105.0
|
||||
|
||||
### What changed?
|
||||
In the Hubspot Trigger, now multiple events can be provided and the field `App ID` was so moved to the credentials.
|
||||
|
||||
### When is action necessary?
|
||||
If you are using the Hubspot Trigger node.
|
||||
|
||||
### How to upgrade:
|
||||
Open the Hubspot Trigger and set the events again. Also open the credentials `Hubspot Developer API` and set your APP ID.
|
||||
|
||||
|
||||
## 0.104.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -20,5 +20,13 @@ export class HubspotDeveloperApi implements ICredentialType {
|
|||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'App ID',
|
||||
name: 'appId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The App ID',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const companyOperations = [
|
||||
{
|
||||
|
@ -63,9 +63,9 @@ export const companyOperations = [
|
|||
|
||||
export const companyFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
|
@ -370,7 +370,7 @@ export const companyFields = [
|
|||
description: 'The main website of the company or organization. This property is used to identify unique companies. Powered by HubSpot Insights.',
|
||||
},
|
||||
{
|
||||
displayName: 'Year Founded',
|
||||
displayName: 'Year Founded',
|
||||
name: 'yearFounded',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
@ -378,9 +378,10 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Company ID',
|
||||
name: 'companyId',
|
||||
|
@ -692,7 +693,7 @@ export const companyFields = [
|
|||
description: 'The main website of the company or organization. This property is used to identify unique companies. Powered by HubSpot Insights.',
|
||||
},
|
||||
{
|
||||
displayName: 'Year Founded',
|
||||
displayName: 'Year Founded',
|
||||
name: 'yearFounded',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
@ -700,9 +701,10 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Company ID',
|
||||
name: 'companyId',
|
||||
|
@ -747,9 +749,10 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -838,9 +841,10 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Company ID',
|
||||
name: 'companyId',
|
||||
|
@ -859,9 +863,10 @@ export const companyFields = [
|
|||
default: '',
|
||||
description: 'Unique identifier for a particular company',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:getRecentlyCreated company:getRecentlyModifie */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:getRecentlyCreated company:getRecentlyModifie */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -939,9 +944,10 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:searchByDomain */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:searchByDomain */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Domain',
|
||||
name: 'domain',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactOperations = [
|
||||
{
|
||||
|
@ -53,9 +53,9 @@ export const contactOperations = [
|
|||
|
||||
export const contactFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
|
@ -121,7 +121,7 @@ export const contactFields = [
|
|||
name: 'associatedCompanyId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod:'getCompanies' ,
|
||||
loadOptionsMethod: 'getCompanies',
|
||||
},
|
||||
default: '',
|
||||
description: 'Companies associated with the ticket',
|
||||
|
@ -501,9 +501,10 @@ export const contactFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
|
@ -603,9 +604,10 @@ export const contactFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -728,9 +730,10 @@ export const contactFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
|
@ -749,9 +752,10 @@ export const contactFields = [
|
|||
default: '',
|
||||
description: 'Unique identifier for a particular contact',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:getRecentlyCreatedUpdated */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:getRecentlyCreatedUpdated */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -875,9 +879,9 @@ export const contactFields = [
|
|||
],
|
||||
},
|
||||
|
||||
//*-------------------------------------------------------------------------- */
|
||||
/* contact:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
//*-------------------------------------------------------------------------- */
|
||||
/* contact:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
|
|
@ -120,6 +120,7 @@ export const contactListFields = [
|
|||
},
|
||||
default: '',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contactList:remove */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export interface IAssociation {
|
||||
associatedCompanyIds?: number[];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const formOperations = [
|
||||
{
|
||||
|
@ -33,9 +33,9 @@ export const formOperations = [
|
|||
|
||||
export const formFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:submit */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:submit */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form',
|
||||
name: 'formId',
|
||||
|
@ -301,9 +301,10 @@ export const formFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:getFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* form:getFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Form',
|
||||
name: 'formId',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export interface IContext {
|
||||
goToWebinarWebinarKey?: string;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -63,7 +63,7 @@ export class Hubspot implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'HubSpot',
|
||||
name: 'hubspot',
|
||||
icon: 'file:hubspot.png',
|
||||
icon: 'file:hubspot.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
|
|
|
@ -5,27 +5,36 @@ import {
|
|||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
companyFields,
|
||||
contactFields,
|
||||
dealFields,
|
||||
hubspotApiRequest,
|
||||
propertyEvents,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
createHash,
|
||||
} from 'crypto';
|
||||
} from 'crypto';
|
||||
|
||||
import {
|
||||
capitalCase,
|
||||
} from 'change-case';
|
||||
|
||||
export class HubspotTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'HubSpot Trigger',
|
||||
name: 'hubspotTrigger',
|
||||
icon: 'file:hubspot.png',
|
||||
icon: 'file:hubspot.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
subtitle: '={{($parameter["appId"]) ? $parameter["event"] : ""}}',
|
||||
description: 'Starts the workflow when HubSpot events occur.',
|
||||
defaults: {
|
||||
name: 'Hubspot Trigger',
|
||||
|
@ -55,87 +64,132 @@ export class HubspotTrigger implements INodeType {
|
|||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'App ID',
|
||||
name: 'appId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'App ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
displayName: 'Events',
|
||||
name: 'eventsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Event',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'contact.creation',
|
||||
value: 'contact.creation',
|
||||
description: `To get notified if any contact is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'contact.deletion',
|
||||
value: 'contact.deletion',
|
||||
description: `To get notified if any contact is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'contact.privacyDeletion',
|
||||
value: 'contact.privacyDeletion',
|
||||
description: `To get notified if a contact is deleted for privacy compliance reasons. `,
|
||||
},
|
||||
{
|
||||
name: 'contact.propertyChange',
|
||||
value: 'contact.propertyChange',
|
||||
description: `to get notified if a specified property is changed for any contact in a customer's account. `,
|
||||
},
|
||||
{
|
||||
name: 'company.creation',
|
||||
value: 'company.creation',
|
||||
description: `To get notified if any company is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'company.deletion',
|
||||
value: 'company.deletion',
|
||||
description: `To get notified if any company is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'company.propertyChange',
|
||||
value: 'company.propertyChange',
|
||||
description: `To get notified if a specified property is changed for any company in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'deal.creation',
|
||||
value: 'deal.creation',
|
||||
description: `To get notified if any deal is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'deal.deletion',
|
||||
value: 'deal.deletion',
|
||||
description: `To get notified if any deal is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'deal.propertyChange',
|
||||
value: 'deal.propertyChange',
|
||||
description: `To get notified if a specified property is changed for any deal in a customer's account.`,
|
||||
},
|
||||
],
|
||||
default: 'contact.creation',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'property',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'contact.propertyChange',
|
||||
'company.propertyChange',
|
||||
'deal.propertyChange',
|
||||
displayName: 'Event',
|
||||
name: 'eventValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact Created',
|
||||
value: 'contact.creation',
|
||||
description: `To get notified if any contact is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Contact Deleted',
|
||||
value: 'contact.deletion',
|
||||
description: `To get notified if any contact is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Contact Privacy Deleted',
|
||||
value: 'contact.privacyDeletion',
|
||||
description: `To get notified if a contact is deleted for privacy compliance reasons. `,
|
||||
},
|
||||
{
|
||||
name: 'Contact Property Changed',
|
||||
value: 'contact.propertyChange',
|
||||
description: `to get notified if a specified property is changed for any contact in a customer's account. `,
|
||||
},
|
||||
{
|
||||
name: 'Company Created',
|
||||
value: 'company.creation',
|
||||
description: `To get notified if any company is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Company Deleted',
|
||||
value: 'company.deletion',
|
||||
description: `To get notified if any company is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Company Property Changed',
|
||||
value: 'company.propertyChange',
|
||||
description: `To get notified if a specified property is changed for any company in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Deal Created',
|
||||
value: 'deal.creation',
|
||||
description: `To get notified if any deal is created in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Deal Deleted',
|
||||
value: 'deal.deletion',
|
||||
description: `To get notified if any deal is deleted in a customer's account.`,
|
||||
},
|
||||
{
|
||||
name: 'Deal Property Changed',
|
||||
value: 'deal.propertyChange',
|
||||
description: `To get notified if a specified property is changed for any deal in a customer's account.`,
|
||||
},
|
||||
],
|
||||
default: 'contact.creation',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'property',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactProperties',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
name: [
|
||||
'contact.propertyChange',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'property',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCompanyProperties',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
name: [
|
||||
'company.propertyChange',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'property',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealProperties',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
name: [
|
||||
'deal.propertyChange',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
|
@ -156,7 +210,62 @@ export class HubspotTrigger implements INodeType {
|
|||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available contacts to display them to user so that he can
|
||||
// select them easily
|
||||
async getContactProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const field of contactFields) {
|
||||
returnData.push({
|
||||
name: capitalCase(field.label),
|
||||
value: field.id,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
// Get all the available companies to display them to user so that he can
|
||||
// select them easily
|
||||
async getCompanyProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const field of companyFields) {
|
||||
returnData.push({
|
||||
name: capitalCase(field.label),
|
||||
value: field.id,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
// Get all the available deals to display them to user so that he can
|
||||
// select them easily
|
||||
async getDealProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const field of dealFields) {
|
||||
returnData.push({
|
||||
name: capitalCase(field.label),
|
||||
value: field.id,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore (because of request)
|
||||
|
@ -165,80 +274,78 @@ export class HubspotTrigger implements INodeType {
|
|||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
// Check all the webhooks which exist already if it is identical to the
|
||||
// one that is supposed to get created.
|
||||
const app = parseInt(this.getNodeParameter('appId') as string, 10);
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const webhookUrlUi = this.getNodeWebhookUrl('default') as string;
|
||||
let endpoint = `/webhooks/v1/${app}/settings`;
|
||||
const { webhookUrl , appId } = await hubspotApiRequest.call(this, 'GET', endpoint, {});
|
||||
endpoint = `/webhooks/v1/${app}/subscriptions`;
|
||||
const subscriptions = await hubspotApiRequest.call(this, 'GET', endpoint, {});
|
||||
for (const subscription of subscriptions) {
|
||||
if (webhookUrl === webhookUrlUi
|
||||
&& appId === app
|
||||
&& subscription.subscriptionDetails.subscriptionType === event
|
||||
&& subscription.enabled === true) {
|
||||
return true;
|
||||
const currentWebhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
|
||||
|
||||
try {
|
||||
const { targetUrl } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/settings`, {});
|
||||
if (targetUrl !== currentWebhookUrl) {
|
||||
throw new Error(`The APP ID ${appId} already has a target url ${targetUrl}. Delete it or use another APP ID before executing the trigger. Due to Hubspot API limitations, you can have just one trigger per APP.`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// if the app is using the current webhook url. Delete everything and create it again with the current events
|
||||
|
||||
const { results: subscriptions } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/subscriptions`, {});
|
||||
|
||||
// delete all subscriptions
|
||||
for (const subscription of subscriptions) {
|
||||
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/subscriptions/${subscription.id}`, {});
|
||||
}
|
||||
|
||||
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/settings`, {});
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const app = this.getNodeParameter('appId') as string;
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
|
||||
const events = (this.getNodeParameter('eventsUi') as IDataObject || {}).eventValues as IDataObject[] || [];
|
||||
const additionalFields = this.getNodeParameter('additionalFields') as IDataObject;
|
||||
const propertyEvents = [
|
||||
'contact.propertyChange',
|
||||
'company.propertyChange',
|
||||
'deal.propertyChange',
|
||||
];
|
||||
let endpoint = `/webhooks/v1/${app}/settings`;
|
||||
let endpoint = `/webhooks/v3/${appId}/settings`;
|
||||
let body: IDataObject = {
|
||||
webhookUrl,
|
||||
targetUrl: webhookUrl,
|
||||
maxConcurrentRequests: additionalFields.maxConcurrentRequests || 5,
|
||||
};
|
||||
|
||||
await hubspotApiRequest.call(this, 'PUT', endpoint, body);
|
||||
|
||||
endpoint = `/webhooks/v1/${app}/subscriptions`;
|
||||
body = {
|
||||
subscriptionDetails: {
|
||||
subscriptionType: event,
|
||||
},
|
||||
enabled: true,
|
||||
};
|
||||
if (propertyEvents.includes(event)) {
|
||||
const property = this.getNodeParameter('property') as string;
|
||||
//@ts-ignore
|
||||
body.subscriptionDetails.propertyName = property;
|
||||
endpoint = `/webhooks/v3/${appId}/subscriptions`;
|
||||
|
||||
if (Array.isArray(events) && events.length === 0) {
|
||||
throw new Error(`You must define at least one event`);
|
||||
}
|
||||
|
||||
const responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
if (responseData.id === undefined) {
|
||||
// Required data is missing so was not successful
|
||||
return false;
|
||||
for (const event of events) {
|
||||
body = {
|
||||
eventType: event.name,
|
||||
active: true,
|
||||
};
|
||||
if (propertyEvents.includes(event.name as string)) {
|
||||
const property = event.property;
|
||||
body.propertyName = property;
|
||||
}
|
||||
await hubspotApiRequest.call(this, 'POST', endpoint, body);
|
||||
}
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
webhookData.webhookId = responseData.id as string;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const app = this.getNodeParameter('appId') as string;
|
||||
if (webhookData.webhookId !== undefined) {
|
||||
const endpoint = `/webhooks/v1/${app}/subscriptions/${webhookData.webhookId}`;
|
||||
const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
|
||||
|
||||
const body = {};
|
||||
const { results: subscriptions } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/subscriptions`, {});
|
||||
|
||||
try {
|
||||
await hubspotApiRequest.call(this, 'DELETE', endpoint, body);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
// Remove from the static workflow data so that it is clear
|
||||
// that no webhooks are registred anymore
|
||||
delete webhookData.webhookId;
|
||||
for (const subscription of subscriptions) {
|
||||
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/subscriptions/${subscription.id}`, {});
|
||||
}
|
||||
|
||||
try {
|
||||
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/settings`, {});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
@ -265,7 +372,7 @@ export class HubspotTrigger implements INodeType {
|
|||
|
||||
if (credentials.clientSecret !== '') {
|
||||
const hash = `${credentials!.clientSecret}${JSON.stringify(bodyData)}`;
|
||||
const signature = createHash('sha256').update(hash).digest('hex');
|
||||
const signature = createHash('sha256').update(hash).digest('hex');
|
||||
//@ts-ignore
|
||||
if (signature !== headerData['x-hubspot-signature']) {
|
||||
return {};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const ticketOperations = [
|
||||
{
|
||||
|
@ -48,9 +48,9 @@ export const ticketOperations = [
|
|||
|
||||
export const ticketFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Pipeline ID',
|
||||
name: 'pipelineId',
|
||||
|
@ -70,7 +70,7 @@ export const ticketFields = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the pipeline the ticket is in. ',
|
||||
description: 'The ID of the pipeline the ticket is in.',
|
||||
},
|
||||
{
|
||||
displayName: 'Stage ID',
|
||||
|
@ -94,7 +94,7 @@ export const ticketFields = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the pipeline the ticket is in. ',
|
||||
description: 'The ID of the pipeline the ticket is in.',
|
||||
},
|
||||
{
|
||||
displayName: 'Ticket Name',
|
||||
|
@ -112,7 +112,7 @@ export const ticketFields = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the pipeline the ticket is in. ',
|
||||
description: 'The ID of the pipeline the ticket is in.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
|
@ -136,7 +136,7 @@ export const ticketFields = [
|
|||
name: 'associatedCompanyIds',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod:'getCompanies' ,
|
||||
loadOptionsMethod: 'getCompanies',
|
||||
},
|
||||
default: [],
|
||||
description: 'Companies associated with the ticket',
|
||||
|
@ -146,7 +146,7 @@ export const ticketFields = [
|
|||
name: 'associatedContactIds',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod:'getContacts' ,
|
||||
loadOptionsMethod: 'getContacts',
|
||||
},
|
||||
default: [],
|
||||
description: 'Contacts associated with the ticket',
|
||||
|
@ -228,9 +228,9 @@ export const ticketFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Ticket ID',
|
||||
name: 'ticketId',
|
||||
|
@ -247,7 +247,7 @@ export const ticketFields = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular ticket',
|
||||
description: 'Unique identifier for a particular ticket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
|
@ -271,20 +271,20 @@ export const ticketFields = [
|
|||
name: 'associatedCompanyIds',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod:'getCompanies' ,
|
||||
loadOptionsMethod: 'getCompanies',
|
||||
},
|
||||
default: [],
|
||||
description: 'Companies associated with the ticket',
|
||||
description: 'Companies associated with the ticket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Ids',
|
||||
name: 'associatedContactIds',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod:'getContacts' ,
|
||||
loadOptionsMethod: 'getContacts',
|
||||
},
|
||||
default: [],
|
||||
description: 'Contact associated with the ticket',
|
||||
description: 'Contact associated with the ticket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Category',
|
||||
|
@ -294,21 +294,21 @@ export const ticketFields = [
|
|||
loadOptionsMethod: 'getTicketCategories',
|
||||
},
|
||||
default: '',
|
||||
description: 'Main reason customer reached out for help',
|
||||
description: 'Main reason customer reached out for help.',
|
||||
},
|
||||
{
|
||||
displayName: 'Close Date',
|
||||
name: 'closeDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The date the ticket was closed',
|
||||
description: 'The date the ticket was closed.',
|
||||
},
|
||||
{
|
||||
displayName: 'Create Date',
|
||||
name: 'createDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'the date the ticket was created',
|
||||
description: 'The date the ticket was created.',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
|
@ -328,7 +328,7 @@ export const ticketFields = [
|
|||
loadOptionsMethod: 'getTicketPipelines',
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the pipeline the ticket is in. ',
|
||||
description: 'The ID of the pipeline the ticket is in.',
|
||||
},
|
||||
{
|
||||
displayName: 'Priority',
|
||||
|
@ -358,7 +358,7 @@ export const ticketFields = [
|
|||
loadOptionsMethod: 'getTicketSources',
|
||||
},
|
||||
default: '',
|
||||
description: 'Channel where ticket was originally submitted',
|
||||
description: 'Channel where ticket was originally submitted.',
|
||||
},
|
||||
{
|
||||
displayName: 'Ticket Name',
|
||||
|
@ -380,176 +380,179 @@ export const ticketFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Ticket ID',
|
||||
name: 'ticketId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include Deleted',
|
||||
name: 'includeDeleted',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'properties',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTicketProperties',
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Ticket ID',
|
||||
name: 'ticketId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
default: [],
|
||||
description: `Used to include specific ticket properties in the results.<br/>
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include Deleted',
|
||||
name: 'includeDeleted',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'properties',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTicketProperties',
|
||||
},
|
||||
default: [],
|
||||
description: `Used to include specific ticket properties in the results.<br/>
|
||||
By default, the results will only include ticket ID and will not include the values for any properties for your tickets.<br/>
|
||||
Including this parameter will include the data for the specified property in the results.<br/>
|
||||
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,<br/>
|
||||
instead of just including the current value. Use this parameter when you need the full history of changes to a property's value.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
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: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 250,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'properties',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTicketProperties',
|
||||
},
|
||||
default: [],
|
||||
description: `Used to include specific ticket properties in the results.<br/>
|
||||
{
|
||||
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,<br/>
|
||||
instead of just including the current value. Use this parameter when you need the full history of changes to a property's value.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
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: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 250,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'properties',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTicketProperties',
|
||||
},
|
||||
default: [],
|
||||
description: `Used to include specific ticket properties in the results.<br/>
|
||||
By default, the results will only include ticket ID and will not include the values for any properties for your tickets.<br/>
|
||||
Including this parameter will include the data for the specified property in the results.<br/>
|
||||
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,<br/>
|
||||
},
|
||||
{
|
||||
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,<br/>
|
||||
instead of just including the current value. Use this parameter when you need the full history of changes to a property's value.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Ticket ID',
|
||||
name: 'ticketId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Ticket ID',
|
||||
name: 'ticketId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular ticket',
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular ticket',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1,022 B |
1
packages/nodes-base/nodes/Hubspot/hubspot.svg
Normal file
1
packages/nodes-base/nodes/Hubspot/hubspot.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 62.883 69.883" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x="2.442" y="2.442"/><symbol id="A" overflow="visible"><path d="M55.504 30.401a16.26 16.26 0 0 0-5.904-5.864c-1.865-1.084-3.794-1.773-5.972-2.07v-7.798c2.161-.895 3.558-3.018 3.525-5.357a5.86 5.86 0 0 0-5.859-5.889 5.91 5.91 0 0 0-5.908 5.889c0 2.393 1.27 4.434 3.452 5.357v7.754c-1.808.262-3.562.812-5.195 1.631L12.769 8.247c.146-.552.273-1.123.273-1.724C13.042 2.92 10.122 0 6.519 0A6.52 6.52 0 0 0 0 6.524c0 3.604 2.92 6.524 6.524 6.524a6.47 6.47 0 0 0 3.35-.952l1.367 1.035 18.726 13.501c-.991.908-1.914 1.943-2.651 3.105-1.494 2.368-2.407 4.971-2.407 7.813v.586c.007 1.927.354 3.838 1.025 5.645.566 1.543 1.396 2.949 2.427 4.219l-6.221 6.235c-1.841-.684-3.906-.23-5.298 1.162-.947.942-1.48 2.227-1.475 3.565s.527 2.612 1.479 3.564 2.227 1.48 3.565 1.48a5.01 5.01 0 0 0 3.565-1.48c.942-.952 1.479-2.227 1.475-3.564a5.03 5.03 0 0 0-.234-1.514l6.426-6.426a16.09 16.09 0 0 0 2.856 1.563 16.7 16.7 0 0 0 6.685 1.406h.439a15.76 15.76 0 0 0 7.627-1.929 15.77 15.77 0 0 0 5.977-5.63c1.499-2.393 2.319-5.044 2.319-7.959v-.146c0-2.866-.664-5.508-2.051-7.93zm-7.847 13.487c-1.743 1.938-3.75 3.135-6.016 3.135h-.43c-1.294 0-2.564-.356-3.799-1.011a8.79 8.79 0 0 1-3.33-3.032c-.898-1.27-1.387-2.656-1.387-4.126v-.439c0-1.445.278-2.817.977-4.111.747-1.465 1.758-2.515 3.101-3.389a7.6 7.6 0 0 1 4.297-1.294h.147c1.416 0 2.769.278 4.038.928 1.286.677 2.378 1.67 3.174 2.886a9.18 9.18 0 0 1 1.421 4.053l.034.913c0 1.987-.762 3.828-2.28 5.498z" stroke="none" fill="#f8761f" fill-rule="nonzero"/></symbol></svg>
|
After Width: | Height: | Size: 1.7 KiB |
Loading…
Reference in a new issue