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:
Ricardo Espinoza 2021-02-01 02:31:40 -05:00 committed by GitHub
parent 5549550928
commit dc98de1ab2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 2303 additions and 373 deletions

View file

@ -2,6 +2,18 @@
This list shows all the versions which include breaking changes and how to upgrade. 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 ## 0.104.0
### What changed? ### What changed?

View file

@ -20,5 +20,13 @@ export class HubspotDeveloperApi implements ICredentialType {
type: 'string' as NodePropertyTypes, type: 'string' as NodePropertyTypes,
default: '', default: '',
}, },
{
displayName: 'App ID',
name: 'appId',
type: 'string' as NodePropertyTypes,
required: true,
default: '',
description: 'The App ID',
},
]; ];
} }

View file

@ -378,6 +378,7 @@ export const companyFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:update */ /* company:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -700,6 +701,7 @@ export const companyFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:get */ /* company:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -747,6 +749,7 @@ export const companyFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:getAll */ /* company:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -838,6 +841,7 @@ export const companyFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:delete */ /* company:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -859,6 +863,7 @@ export const companyFields = [
default: '', default: '',
description: 'Unique identifier for a particular company', description: 'Unique identifier for a particular company',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:getRecentlyCreated company:getRecentlyModifie */ /* company:getRecentlyCreated company:getRecentlyModifie */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -939,6 +944,7 @@ export const companyFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* company:searchByDomain */ /* company:searchByDomain */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -501,6 +501,7 @@ export const contactFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:get */ /* contact:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -603,6 +604,7 @@ export const contactFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:getAll */ /* contact:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -728,6 +730,7 @@ export const contactFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:delete */ /* contact:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -749,6 +752,7 @@ export const contactFields = [
default: '', default: '',
description: 'Unique identifier for a particular contact', description: 'Unique identifier for a particular contact',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:getRecentlyCreatedUpdated */ /* contact:getRecentlyCreatedUpdated */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -120,6 +120,7 @@ export const contactListFields = [
}, },
default: '', default: '',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contactList:remove */ /* contactList:remove */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -301,6 +301,7 @@ export const formFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* form:getFields */ /* form:getFields */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

File diff suppressed because it is too large Load diff

View file

@ -63,7 +63,7 @@ export class Hubspot implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'HubSpot', displayName: 'HubSpot',
name: 'hubspot', name: 'hubspot',
icon: 'file:hubspot.png', icon: 'file:hubspot.svg',
group: ['output'], group: ['output'],
version: 1, version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

View file

@ -5,27 +5,36 @@ import {
import { import {
IDataObject, IDataObject,
ILoadOptionsFunctions,
INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
IWebhookResponseData, IWebhookResponseData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
companyFields,
contactFields,
dealFields,
hubspotApiRequest, hubspotApiRequest,
propertyEvents,
} from './GenericFunctions'; } from './GenericFunctions';
import { import {
createHash, createHash,
} from 'crypto'; } from 'crypto';
import {
capitalCase,
} from 'change-case';
export class HubspotTrigger implements INodeType { export class HubspotTrigger implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'HubSpot Trigger', displayName: 'HubSpot Trigger',
name: 'hubspotTrigger', name: 'hubspotTrigger',
icon: 'file:hubspot.png', icon: 'file:hubspot.svg',
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
subtitle: '={{($parameter["appId"]) ? $parameter["event"] : ""}}',
description: 'Starts the workflow when HubSpot events occur.', description: 'Starts the workflow when HubSpot events occur.',
defaults: { defaults: {
name: 'Hubspot Trigger', name: 'Hubspot Trigger',
@ -55,65 +64,71 @@ export class HubspotTrigger implements INodeType {
], ],
properties: [ properties: [
{ {
displayName: 'App ID', displayName: 'Events',
name: 'appId', name: 'eventsUi',
type: 'string', type: 'fixedCollection',
default: '', typeOptions: {
required: true, multipleValues: true,
description: 'App ID',
}, },
placeholder: 'Add Event',
default: {},
options: [
{ {
displayName: 'Event', displayName: 'Event',
name: 'event', name: 'eventValues',
values: [
{
displayName: 'Name',
name: 'name',
type: 'options', type: 'options',
options: [ options: [
{ {
name: 'contact.creation', name: 'Contact Created',
value: 'contact.creation', value: 'contact.creation',
description: `To get notified if any contact is created in a customer's account.`, description: `To get notified if any contact is created in a customer's account.`,
}, },
{ {
name: 'contact.deletion', name: 'Contact Deleted',
value: 'contact.deletion', value: 'contact.deletion',
description: `To get notified if any contact is deleted in a customer's account.`, description: `To get notified if any contact is deleted in a customer's account.`,
}, },
{ {
name: 'contact.privacyDeletion', name: 'Contact Privacy Deleted',
value: 'contact.privacyDeletion', value: 'contact.privacyDeletion',
description: `To get notified if a contact is deleted for privacy compliance reasons. `, description: `To get notified if a contact is deleted for privacy compliance reasons. `,
}, },
{ {
name: 'contact.propertyChange', name: 'Contact Property Changed',
value: 'contact.propertyChange', value: 'contact.propertyChange',
description: `to get notified if a specified property is changed for any contact in a customer's account. `, description: `to get notified if a specified property is changed for any contact in a customer's account. `,
}, },
{ {
name: 'company.creation', name: 'Company Created',
value: 'company.creation', value: 'company.creation',
description: `To get notified if any company is created in a customer's account.`, description: `To get notified if any company is created in a customer's account.`,
}, },
{ {
name: 'company.deletion', name: 'Company Deleted',
value: 'company.deletion', value: 'company.deletion',
description: `To get notified if any company is deleted in a customer's account.`, description: `To get notified if any company is deleted in a customer's account.`,
}, },
{ {
name: 'company.propertyChange', name: 'Company Property Changed',
value: 'company.propertyChange', value: 'company.propertyChange',
description: `To get notified if a specified property is changed for any company in a customer's account.`, description: `To get notified if a specified property is changed for any company in a customer's account.`,
}, },
{ {
name: 'deal.creation', name: 'Deal Created',
value: 'deal.creation', value: 'deal.creation',
description: `To get notified if any deal is created in a customer's account.`, description: `To get notified if any deal is created in a customer's account.`,
}, },
{ {
name: 'deal.deletion', name: 'Deal Deleted',
value: 'deal.deletion', value: 'deal.deletion',
description: `To get notified if any deal is deleted in a customer's account.`, description: `To get notified if any deal is deleted in a customer's account.`,
}, },
{ {
name: 'deal.propertyChange', name: 'Deal Property Changed',
value: 'deal.propertyChange', value: 'deal.propertyChange',
description: `To get notified if a specified property is changed for any deal in a customer's account.`, description: `To get notified if a specified property is changed for any deal in a customer's account.`,
}, },
@ -124,12 +139,47 @@ export class HubspotTrigger implements INodeType {
{ {
displayName: 'Property', displayName: 'Property',
name: 'property', name: 'property',
type: 'string', type: 'options',
typeOptions: {
loadOptionsMethod: 'getContactProperties',
},
displayOptions: { displayOptions: {
show: { show: {
event: [ name: [
'contact.propertyChange', 'contact.propertyChange',
],
},
},
default: '',
required: true,
},
{
displayName: 'Property',
name: 'property',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCompanyProperties',
},
displayOptions: {
show: {
name: [
'company.propertyChange', 'company.propertyChange',
],
},
},
default: '',
required: true,
},
{
displayName: 'Property',
name: 'property',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDealProperties',
},
displayOptions: {
show: {
name: [
'deal.propertyChange', 'deal.propertyChange',
], ],
}, },
@ -137,6 +187,10 @@ export class HubspotTrigger implements INodeType {
default: '', default: '',
required: true, required: true,
}, },
],
},
],
},
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
@ -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) // @ts-ignore (because of request)
@ -165,81 +274,79 @@ export class HubspotTrigger implements INodeType {
async checkExists(this: IHookFunctions): Promise<boolean> { async checkExists(this: IHookFunctions): Promise<boolean> {
// Check all the webhooks which exist already if it is identical to the // Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created. // one that is supposed to get created.
const app = parseInt(this.getNodeParameter('appId') as string, 10); const currentWebhookUrl = this.getNodeWebhookUrl('default') as string;
const event = this.getNodeParameter('event') as string; const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
const webhookUrlUi = this.getNodeWebhookUrl('default') as string;
let endpoint = `/webhooks/v1/${app}/settings`; try {
const { webhookUrl , appId } = await hubspotApiRequest.call(this, 'GET', endpoint, {}); const { targetUrl } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/settings`, {});
endpoint = `/webhooks/v1/${app}/subscriptions`; if (targetUrl !== currentWebhookUrl) {
const subscriptions = await hubspotApiRequest.call(this, 'GET', endpoint, {}); 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) { for (const subscription of subscriptions) {
if (webhookUrl === webhookUrlUi await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/subscriptions/${subscription.id}`, {});
&& appId === app
&& subscription.subscriptionDetails.subscriptionType === event
&& subscription.enabled === true) {
return true;
}
} }
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/settings`, {});
return false; return false;
}, },
async create(this: IHookFunctions): Promise<boolean> { async create(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default'); const webhookUrl = this.getNodeWebhookUrl('default');
const app = this.getNodeParameter('appId') as string; const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
const event = this.getNodeParameter('event') as string; const events = (this.getNodeParameter('eventsUi') as IDataObject || {}).eventValues as IDataObject[] || [];
const additionalFields = this.getNodeParameter('additionalFields') as IDataObject; const additionalFields = this.getNodeParameter('additionalFields') as IDataObject;
const propertyEvents = [ let endpoint = `/webhooks/v3/${appId}/settings`;
'contact.propertyChange',
'company.propertyChange',
'deal.propertyChange',
];
let endpoint = `/webhooks/v1/${app}/settings`;
let body: IDataObject = { let body: IDataObject = {
webhookUrl, targetUrl: webhookUrl,
maxConcurrentRequests: additionalFields.maxConcurrentRequests || 5, maxConcurrentRequests: additionalFields.maxConcurrentRequests || 5,
}; };
await hubspotApiRequest.call(this, 'PUT', endpoint, body); await hubspotApiRequest.call(this, 'PUT', endpoint, body);
endpoint = `/webhooks/v1/${app}/subscriptions`; endpoint = `/webhooks/v3/${appId}/subscriptions`;
if (Array.isArray(events) && events.length === 0) {
throw new Error(`You must define at least one event`);
}
for (const event of events) {
body = { body = {
subscriptionDetails: { eventType: event.name,
subscriptionType: event, active: true,
},
enabled: true,
}; };
if (propertyEvents.includes(event)) { if (propertyEvents.includes(event.name as string)) {
const property = this.getNodeParameter('property') as string; const property = event.property;
//@ts-ignore body.propertyName = property;
body.subscriptionDetails.propertyName = property; }
await hubspotApiRequest.call(this, 'POST', endpoint, body);
} }
const responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body);
if (responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}
const webhookData = this.getWorkflowStaticData('node');
webhookData.webhookId = responseData.id as string;
return true; return true;
}, },
async delete(this: IHookFunctions): Promise<boolean> { async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node'); const { appId } = this.getCredentials('hubspotDeveloperApi') as IDataObject;
const app = this.getNodeParameter('appId') as string;
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhooks/v1/${app}/subscriptions/${webhookData.webhookId}`;
const body = {}; const { results: subscriptions } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/subscriptions`, {});
for (const subscription of subscriptions) {
await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/subscriptions/${subscription.id}`, {});
}
try { try {
await hubspotApiRequest.call(this, 'DELETE', endpoint, body); await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/settings`, {});
} catch (e) { } catch (e) {
return false; return false;
} }
// Remove from the static workflow data so that it is clear
// that no webhooks are registred anymore
delete webhookData.webhookId;
}
return true; return true;
}, },
}, },

View file

@ -247,7 +247,7 @@ export const ticketFields = [
}, },
}, },
default: '', default: '',
description: 'Unique identifier for a particular ticket', description: 'Unique identifier for a particular ticket.',
}, },
{ {
displayName: 'Update Fields', displayName: 'Update Fields',
@ -274,7 +274,7 @@ export const ticketFields = [
loadOptionsMethod: 'getCompanies', loadOptionsMethod: 'getCompanies',
}, },
default: [], default: [],
description: 'Companies associated with the ticket', description: 'Companies associated with the ticket.',
}, },
{ {
displayName: 'Contact Ids', displayName: 'Contact Ids',
@ -284,7 +284,7 @@ export const ticketFields = [
loadOptionsMethod: 'getContacts', loadOptionsMethod: 'getContacts',
}, },
default: [], default: [],
description: 'Contact associated with the ticket', description: 'Contact associated with the ticket.',
}, },
{ {
displayName: 'Category', displayName: 'Category',
@ -294,21 +294,21 @@ export const ticketFields = [
loadOptionsMethod: 'getTicketCategories', loadOptionsMethod: 'getTicketCategories',
}, },
default: '', default: '',
description: 'Main reason customer reached out for help', description: 'Main reason customer reached out for help.',
}, },
{ {
displayName: 'Close Date', displayName: 'Close Date',
name: 'closeDate', name: 'closeDate',
type: 'dateTime', type: 'dateTime',
default: '', default: '',
description: 'The date the ticket was closed', description: 'The date the ticket was closed.',
}, },
{ {
displayName: 'Create Date', displayName: 'Create Date',
name: 'createDate', name: 'createDate',
type: 'dateTime', type: 'dateTime',
default: '', default: '',
description: 'the date the ticket was created', description: 'The date the ticket was created.',
}, },
{ {
displayName: 'Description', displayName: 'Description',
@ -358,7 +358,7 @@ export const ticketFields = [
loadOptionsMethod: 'getTicketSources', loadOptionsMethod: 'getTicketSources',
}, },
default: '', default: '',
description: 'Channel where ticket was originally submitted', description: 'Channel where ticket was originally submitted.',
}, },
{ {
displayName: 'Ticket Name', displayName: 'Ticket Name',
@ -380,6 +380,7 @@ export const ticketFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* ticket:get */ /* ticket:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -447,6 +448,7 @@ export const ticketFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* ticket:getAll */ /* ticket:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -531,6 +533,7 @@ export const ticketFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* ticket:delete */ /* ticket:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,022 B

View 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