-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: Update "@hubspot/api-client" from 6.0.1
to 12.0.1
CAL-[5029]
#18670
base: main
Are you sure you want to change the base?
Changes from all commits
8f1fa2b
99bd587
2add85d
86f2edb
7cda8d6
276f7e5
c62394b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,20 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import * as hubspot from "@hubspot/api-client"; | ||
import type { BatchInputPublicAssociation } from "@hubspot/api-client/lib/codegen/crm/associations"; | ||
import type { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts"; | ||
import { Client, AssociationTypes } from "@hubspot/api-client"; | ||
import { FilterOperatorEnum as CompanyFilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/companies"; | ||
import type { | ||
PublicObjectSearchRequest as ContactSearchInput, | ||
SimplePublicObjectInputForCreate as HubspotContactCreateInput, | ||
PublicObjectSearchRequest as CompanySearchInput, | ||
} from "@hubspot/api-client/lib/codegen/crm/contacts"; | ||
import { FilterOperatorEnum as ContactFilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; | ||
import { AssociationSpecAssociationCategoryEnum as ContactAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; | ||
import type { | ||
SimplePublicObject, | ||
SimplePublicObjectInput, | ||
SimplePublicObjectInputForCreate as MeetingCreateInput, | ||
PublicAssociationsForObject, | ||
} from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; | ||
import { AssociationSpecAssociationCategoryEnum as MeetingAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; | ||
import type z from "zod"; | ||
|
||
import { getLocation } from "@calcom/lib/CalEventParser"; | ||
import { WEBAPP_URL } from "@calcom/lib/constants"; | ||
|
@@ -19,27 +28,25 @@ import type { CRM, ContactCreateInput, Contact, CrmEvent } from "@calcom/types/C | |
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; | ||
import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; | ||
import type { HubspotToken } from "../api/callback"; | ||
import type { appDataSchema } from "../zod"; | ||
|
||
const hubspotClient = new hubspot.Client(); | ||
|
||
interface CustomPublicObjectInput extends SimplePublicObjectInput { | ||
id?: string; | ||
} | ||
const hubspotClient = new Client(); | ||
|
||
export default class HubspotCalendarService implements CRM { | ||
private url = ""; | ||
private integrationName = ""; | ||
private auth: Promise<{ getToken: () => Promise<HubspotToken | void | never[]> }>; | ||
private log: typeof logger; | ||
private client_id = ""; | ||
private client_secret = ""; | ||
private appOptions: z.infer<typeof appDataSchema>; | ||
|
||
constructor(credential: CredentialPayload) { | ||
constructor(credential: CredentialPayload, appOptions: any) { | ||
this.integrationName = "hubspot_other_calendar"; | ||
|
||
this.auth = this.hubspotAuth(credential).then((r) => r); | ||
|
||
this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] }); | ||
this.appOptions = appOptions; | ||
} | ||
|
||
private getHubspotMeetingBody = (event: CalendarEvent): string => { | ||
|
@@ -50,35 +57,40 @@ export default class HubspotCalendarService implements CRM { | |
}`; | ||
}; | ||
|
||
private hubspotCreateMeeting = async (event: CalendarEvent) => { | ||
const simplePublicObjectInput: SimplePublicObjectInput = { | ||
properties: { | ||
hs_timestamp: Date.now().toString(), | ||
hs_meeting_title: event.title, | ||
hs_meeting_body: this.getHubspotMeetingBody(event), | ||
hs_meeting_location: getLocation(event), | ||
hs_meeting_start_time: new Date(event.startTime).toISOString(), | ||
hs_meeting_end_time: new Date(event.endTime).toISOString(), | ||
hs_meeting_outcome: "SCHEDULED", | ||
}, | ||
}; | ||
|
||
return hubspotClient.crm.objects.meetings.basicApi.create(simplePublicObjectInput); | ||
}; | ||
private hubspotCreateMeeting = async (event: CalendarEvent, contacts: Contact[]) => { | ||
try { | ||
const simplePublicObjectInput: MeetingCreateInput = { | ||
properties: { | ||
hs_timestamp: Date.now().toString(), | ||
hs_meeting_title: event.title, | ||
hs_meeting_body: this.getHubspotMeetingBody(event), | ||
hs_meeting_location: getLocation(event), | ||
hs_meeting_start_time: new Date(event.startTime).toISOString(), | ||
hs_meeting_end_time: new Date(event.endTime).toISOString(), | ||
hs_meeting_outcome: "SCHEDULED", | ||
}, | ||
associations: contacts.reduce((associations, contact) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of making a separate call to associate a meeting with a contact, we can do it in one call. It'll actually throw a type error if we don't. |
||
if (contact.id) { | ||
associations.push({ | ||
to: { | ||
id: contact.id, | ||
}, | ||
types: [ | ||
{ | ||
associationCategory: MeetingAssociationCategoryEnum.HubspotDefined, | ||
associationTypeId: AssociationTypes.meetingToContact, | ||
}, | ||
], | ||
}); | ||
} | ||
return associations; | ||
}, [] as PublicAssociationsForObject[]), | ||
}; | ||
|
||
private hubspotAssociate = async (meeting: SimplePublicObject, contacts: Array<{ id: string }>) => { | ||
const batchInputPublicAssociation: BatchInputPublicAssociation = { | ||
inputs: contacts.map((contact: { id: string }) => ({ | ||
_from: { id: meeting.id }, | ||
to: { id: contact.id }, | ||
type: "meeting_event_to_contact", | ||
})), | ||
}; | ||
return hubspotClient.crm.associations.batchApi.create( | ||
"meetings", | ||
"contacts", | ||
batchInputPublicAssociation | ||
); | ||
return await hubspotClient.crm.objects.meetings.basicApi.create(simplePublicObjectInput); | ||
} catch (e) { | ||
this.log.warn(`error creating event for bookingUid ${event.uid}, ${e}`); | ||
} | ||
}; | ||
|
||
private hubspotUpdateMeeting = async (uid: string, event: CalendarEvent) => { | ||
|
@@ -120,7 +132,7 @@ export default class HubspotCalendarService implements CRM { | |
try { | ||
const hubspotRefreshToken: HubspotToken = await refreshOAuthTokens( | ||
async () => | ||
await hubspotClient.oauth.tokensApi.createToken( | ||
await hubspotClient.oauth.tokensApi.create( | ||
"refresh_token", | ||
undefined, | ||
`${WEBAPP_URL}/api/integrations/hubspot/callback`, | ||
|
@@ -156,23 +168,18 @@ export default class HubspotCalendarService implements CRM { | |
}; | ||
|
||
async handleMeetingCreation(event: CalendarEvent, contacts: Contact[]) { | ||
const contactIds: { id?: string }[] = contacts.map((contact) => ({ id: contact.id })); | ||
const meetingEvent = await this.hubspotCreateMeeting(event); | ||
const meetingEvent = await this.hubspotCreateMeeting(event, contacts); | ||
|
||
if (meetingEvent) { | ||
this.log.debug("meeting:creation:ok", { meetingEvent }); | ||
const associatedMeeting = await this.hubspotAssociate(meetingEvent, contactIds as any); | ||
if (associatedMeeting) { | ||
this.log.debug("association:creation:ok", { associatedMeeting }); | ||
return Promise.resolve({ | ||
uid: meetingEvent.id, | ||
id: meetingEvent.id, | ||
type: "hubspot_other_calendar", | ||
password: "", | ||
url: "", | ||
additionalInfo: { contacts, associatedMeeting }, | ||
}); | ||
} | ||
return Promise.reject("Something went wrong when associating the meeting and attendees in HubSpot"); | ||
return Promise.resolve({ | ||
uid: meetingEvent.id, | ||
id: meetingEvent.id, | ||
type: "hubspot_other_calendar", | ||
password: "", | ||
url: "", | ||
additionalInfo: { contacts, meetingEvent }, | ||
}); | ||
} | ||
this.log.debug("meeting:creation:notOk", { meetingEvent, event, contacts }); | ||
return Promise.reject("Something went wrong when creating a meeting in HubSpot"); | ||
|
@@ -204,20 +211,19 @@ export default class HubspotCalendarService implements CRM { | |
|
||
const emailArray = Array.isArray(emails) ? emails : [emails]; | ||
|
||
const publicObjectSearchRequest: PublicObjectSearchRequest = { | ||
const publicObjectSearchRequest: ContactSearchInput = { | ||
filterGroups: emailArray.map((attendeeEmail) => ({ | ||
filters: [ | ||
{ | ||
value: attendeeEmail, | ||
propertyName: "email", | ||
operator: "EQ", | ||
operator: ContactFilterOperatorEnum.Eq, | ||
}, | ||
], | ||
})), | ||
sorts: ["hs_object_id"], | ||
properties: ["hs_object_id", "email"], | ||
limit: 10, | ||
after: 0, | ||
}; | ||
|
||
const contacts = await hubspotClient.crm.contacts.searchApi | ||
|
@@ -227,7 +233,7 @@ export default class HubspotCalendarService implements CRM { | |
return contacts.map((contact) => { | ||
return { | ||
id: contact.id, | ||
email: contact.properties.email, | ||
email: contact.properties.email || "", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Emails are required for contacts so not sure why it's throwing a |
||
}; | ||
}); | ||
} | ||
|
@@ -236,14 +242,61 @@ export default class HubspotCalendarService implements CRM { | |
const auth = await this.auth; | ||
await auth.getToken(); | ||
|
||
const simplePublicObjectInputs = contactsToCreate.map((attendee) => { | ||
const appOptions = this.getAppOptions(); | ||
let companyId: string; | ||
|
||
// Check for a company to associate the contact with | ||
if (appOptions?.createContactUnderCompany) { | ||
const emailDomain = contactsToCreate[0].email.split("@")[1]; | ||
|
||
const companySearchInput: CompanySearchInput = { | ||
filterGroups: [ | ||
{ | ||
filters: [ | ||
{ | ||
propertyName: "website", | ||
operator: CompanyFilterOperatorEnum.ContainsToken, | ||
value: emailDomain, | ||
}, | ||
], | ||
}, | ||
], | ||
properties: ["id"], | ||
limit: 1, | ||
}; | ||
|
||
const companyQuery = await hubspotClient.crm.companies.searchApi | ||
.doSearch(companySearchInput) | ||
.then((apiResponse) => apiResponse.results); | ||
if (companyQuery.length > 0) { | ||
const company = companyQuery[0]; | ||
companyId = company.id; | ||
} | ||
} | ||
|
||
const simplePublicObjectInputs: HubspotContactCreateInput[] = contactsToCreate.map((attendee) => { | ||
const [firstname, lastname] = attendee.name ? attendee.name.split(" ") : [attendee.email, ""]; | ||
return { | ||
properties: { | ||
firstname, | ||
lastname, | ||
email: attendee.email, | ||
}, | ||
...(companyId && { | ||
associations: [ | ||
{ | ||
to: { | ||
id: companyId, | ||
}, | ||
types: [ | ||
{ | ||
associationCategory: ContactAssociationCategoryEnum.HubspotDefined, | ||
associationTypeId: AssociationTypes.contactToCompany, | ||
}, | ||
], | ||
}, | ||
], | ||
}), | ||
}; | ||
}); | ||
const createdContacts = await Promise.all( | ||
|
@@ -265,12 +318,12 @@ export default class HubspotCalendarService implements CRM { | |
return createdContacts.map((contact) => { | ||
return { | ||
id: contact.id, | ||
email: contact.properties.email, | ||
email: contact.properties.email || "", | ||
}; | ||
}); | ||
} | ||
|
||
getAppOptions() { | ||
console.log("No options implemented"); | ||
return this.appOptions; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change in method from
.createToken()
tocreate()