From 89033c0e9c853cdcf0ba88806ac4952c740f243c Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:25:38 -0500 Subject: [PATCH] chore: Salesforce add more logging (#19106) * Add more logs * Remove unused method * Fallback when creating an event * Type fix * Type fix * Type fix * Add more logging * More logging --- .../app-store/salesforce/lib/CrmService.ts | 116 +++++++++++++----- 1 file changed, 83 insertions(+), 33 deletions(-) diff --git a/packages/app-store/salesforce/lib/CrmService.ts b/packages/app-store/salesforce/lib/CrmService.ts index 153e14e576..4daebde17c 100644 --- a/packages/app-store/salesforce/lib/CrmService.ts +++ b/packages/app-store/salesforce/lib/CrmService.ts @@ -180,11 +180,14 @@ export default class SalesforceCRMService implements CRM { }; private salesforceCreateEvent = async (event: CalendarEvent, contacts: Contact[]) => { + const log = logger.getSubLogger({ prefix: [`[salesforceCreateEvent]:${event.uid}`] }); + const appOptions = this.getAppOptions(); const writeToEventRecord = await this.generateWriteToEventBody(event); + log.info(`Writing to event fields: ${Object.keys(writeToEventRecord)} `); - let ownerId = null; + let ownerId: string | undefined = undefined; if (event?.organizer?.email) { ownerId = await this.getSalesforceUserIdFromEmail(event.organizer.email); } else { @@ -201,8 +204,20 @@ export default class SalesforceCRMService implements CRM { this.log.warn("salesforceCreateEvent: No contacts found for event", event.uid, contacts); } + const eventWhoIds = contacts.reduce((contactIds, contact) => { + if (contact?.id) { + contactIds.push(contact.id); + } + + return contactIds; + }, [] as string[]); + + if (eventWhoIds.length !== contacts.length) { + log.warn(`Not all contacts contain ids ${contacts}`); + } + const createdEvent = await this.salesforceCreateEventApiCall(event, { - EventWhoIds: contacts.map((contact) => contact.id), + EventWhoIds: eventWhoIds, ...writeToEventRecord, ...(ownerId && { OwnerId: ownerId }), }).catch(async (reason) => { @@ -218,12 +233,27 @@ export default class SalesforceCRMService implements CRM { WhoId: firstContact, }).catch((reason) => Promise.reject(reason)); } - return Promise.reject(reason); + log.error(`Error creating event: ${JSON.stringify(reason)}`); + + // Try creating a simple object without additional records + return await this.salesforceCreateEventApiCall(event, { + EventWhoIds: eventWhoIds, + ...(ownerId && { OwnerId: ownerId }), + }).catch((reason) => { + log.error(`Error creating simple event: ${JSON.stringify(reason)}`); + return Promise.reject(reason); + }); }); // Check to see if we also need to change the record owner - if (appOptions.onBookingChangeRecordOwner && appOptions.onBookingChangeRecordOwnerName && ownerId) { - // TODO: firstContact id is assumed to not be undefined. But current code doesn't check for it. - await this.checkRecordOwnerNameFromRecordId(firstContact.id, ownerId); + if (appOptions.onBookingChangeRecordOwner && appOptions.onBookingChangeRecordOwnerName) { + if (ownerId) { + // TODO: firstContact id is assumed to not be undefined. But current code doesn't check for it. + await this.checkRecordOwnerNameFromRecordId(firstContact.id, ownerId); + } else { + this.log.warn( + `Could not find owner with email ${event.organizer.email} to change record ${firstContact.id} ownership to` + ); + } } if (appOptions.onBookingWriteToRecord && appOptions.onBookingWriteToRecordFields) { await this.writeToPersonRecord( @@ -472,6 +502,7 @@ export default class SalesforceCRMService implements CRM { if (!contactsToCreate[0]?.email) { this.log.warn(`createContact: no attendee email found `, contactsToCreate); + return []; } if (createEventOn === SalesforceRecordEnum.LEAD) { @@ -787,36 +818,44 @@ export default class SalesforceCRMService implements CRM { const conn = await this.conn; const appOptions = this.getAppOptions(); - if (!appOptions?.createEventOn) { + this.log.info(`Changing owner for record ${id}`); + + const recordType = this.determineRecordTypeById(id); + + if (!appOptions?.onBookingChangeRecordOwnerName) { this.log.warn( - `No appOptions.createEventOn found for ${this.integrationName} on checkRecordOwnerNameFromRecordId` + `No appOptions.onBookingChangeRecordOwnerName found for ${this.integrationName} on checkRecordOwnerNameFromRecordId` ); return; } // Get the associated record that the event was created on const recordQuery = (await conn.query( - `SELECT OwnerId FROM ${appOptions.createEventOn} WHERE Id = '${id}'` - )) as { records: { OwnerId: string }[] }; + `SELECT OwnerId, Owner.Name FROM ${recordType} WHERE Id = '${id}'` + )) as { records: { OwnerId: string; Owner: { Name: string } }[] }; - if (!recordQuery || !recordQuery.records.length) return; + if (!recordQuery || !recordQuery.records.length) { + this.log.warn(`Could not find record for id ${id} and type ${recordType}`); + } - const ownerId = recordQuery.records[0].OwnerId; + const owner = recordQuery.records[0].Owner; - const ownerQuery = await this.getSalesforceUserFromUserId(ownerId); + if (!appOptions.onBookingChangeRecordOwnerName.includes(owner?.Name)) { + this.log.warn( + `Current owner name ${owner?.Name} for record ${id} does not match the option ${appOptions.onBookingChangeRecordOwnerName}` + ); + } - if (!ownerQuery || !ownerQuery.records.length) return; - - const owner = ownerQuery.records[0] as { Name: string }; - - // Check that the owner name matches the names where we need to change the organizer - if (appOptions?.onBookingChangeRecordOwnerName.includes(owner.Name)) { - await conn.sobject(appOptions?.createEventOn).update({ + await conn + .sobject(appOptions?.createEventOn) + .update({ // First field is there WHERE statement Id: id, OwnerId: newOwnerId, + }) + .catch((error) => { + this.log.warn(`Error changing owner name with error ${JSON.stringify(error)}`); }); - } } private async getAccountIdBasedOnEmailDomainOfContacts(email: string) { @@ -901,9 +940,9 @@ export default class SalesforceCRMService implements CRM { bookingUid?: string | null ) { const conn = await this.conn; - const { createEventOn, onBookingWriteToRecordFields = {} } = this.getAppOptions(); + const { onBookingWriteToRecordFields = {} } = this.getAppOptions(); // Determine record type (Contact or Lead) - const personRecordType = this.determinePersonRecordType(createEventOn); + const personRecordType = this.determineRecordTypeById(contactId); // Search the fields and ensure 1. they exist 2. they're the right type const fieldsToWriteOn = Object.keys(onBookingWriteToRecordFields); const existingFields = await this.ensureFieldsExistOnObject(fieldsToWriteOn, personRecordType); @@ -931,7 +970,9 @@ export default class SalesforceCRMService implements CRM { calEventResponses, }); - this.log.info(`Final writeOnRecordBody contains fields ${Object.keys(writeOnRecordBody)}`); + this.log.info( + `Final writeOnRecordBody contains fields ${Object.keys(writeOnRecordBody)} for record ${contactId}` + ); // Update the person record await conn @@ -970,7 +1011,7 @@ export default class SalesforceCRMService implements CRM { // Skip if field should only be written when empty and already has a value if (fieldConfig.whenToWrite === WhenToWriteToRecord.FIELD_EMPTY && personRecord[field.name]) { this.log.info( - `Writing to field ${field.name} on contactId ${personRecord?.id} with value ${ + `Field ${field.name} on contactId ${personRecord?.Id} already exists with value ${ personRecord[field.name] }` ); @@ -1149,14 +1190,6 @@ export default class SalesforceCRMService implements CRM { return null; } - private determinePersonRecordType(createEventOn: string): SalesforceRecordEnum { - return createEventOn === SalesforceRecordEnum.LEAD && - this.appOptions.createEventOnLeadCheckForContact && - this.getFallbackToContact() - ? SalesforceRecordEnum.CONTACT - : this.appOptions.createEventOn; - } - private async fetchPersonRecord( contactId: string, existingFields: Field[], @@ -1342,4 +1375,21 @@ export default class SalesforceCRMService implements CRM { throw new Error(errorMessage); }); } + + /** All salesforce ids have a 3 character prefix associated with the record type + * https://help.salesforce.com/s/articleView?id=000385203&type=1 + */ + private determineRecordTypeById(id: string) { + switch (id.substring(0, 3)) { + case "003": + return SalesforceRecordEnum.CONTACT; + case "001": + return SalesforceRecordEnum.ACCOUNT; + case "00Q": + return SalesforceRecordEnum.LEAD; + default: + this.log.warn(`Unhandled record id type ${id}`); + return SalesforceRecordEnum.CONTACT; + } + } }