chore: Salesforce - optimize getContact method [CAL-4940] (#18289)

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Joe Au-Yeung
2025-01-09 12:14:49 -07:00
committed by GitHub
co-authored by Udit Takkar
parent b6bd0eda43
commit a22fd0c151
4 changed files with 129 additions and 169 deletions
@@ -166,6 +166,13 @@ describe("getTeamMemberEmailForResponseOrContactUsingUrlQuery", () => {
schedulingType: SchedulingType.ROUND_ROBIN,
metadata: null,
length: 30,
hosts: [
{
user: {
email: "owner@example.com",
},
},
],
};
it("should return null when email is not provided in query", async () => {
@@ -235,7 +242,7 @@ describe("getTeamMemberEmailForResponseOrContactUsingUrlQuery", () => {
});
it("should return null when CRM owner is not part of event type", async () => {
const ownerEmail = "owner@example.com";
const ownerEmail = "ownerNotInEventType@example.com";
const bookerEmail = "booker@example.com";
mockGetCRMContactOwnerForRRLeadSkip({ bookerEmail, teamMemberEmail: ownerEmail });
@@ -331,7 +338,7 @@ describe("getTeamMemberEmailForResponseOrContactUsingUrlQuery", () => {
it("should return null when when cal.routingFormResponseId and cal.salesforce.xxxx=true is provided but the returned email isn't an event member", async () => {
const bookerEmail = "booker@example.com";
const teamMemberEmail = "owner@example.com";
const teamMemberEmail = "ownerNotInEventType@example.com";
mockBookingFormHandler({ bookerEmail, teamMemberEmail });
const SOME_OTHER_EVENT_ID = 200;
await createHostForEvent({
@@ -92,6 +92,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
select: {
name: true,
username: true,
email: true,
},
},
},
@@ -18,6 +18,9 @@ const contactQueryResponse = {
Email: "test@example.com",
OwnerId: "owner001",
attributes: { type: "Contact" },
Owner: {
Email: "owner@example.com",
},
},
],
};
@@ -30,6 +33,12 @@ const contactUnderAccountQueryResponse = {
OwnerId: "owner001",
AccountId: "acc001",
attributes: { type: "Contact" },
Account: {
attributes: { type: "Account" },
Owner: {
Email: "owner@example.com",
},
},
},
],
};
@@ -41,6 +50,9 @@ const leadQueryResponse = {
Email: "test@example.com",
OwnerId: "owner001",
attributes: { type: "Lead" },
Owner: {
Email: "owner@example.com",
},
},
],
};
@@ -114,7 +126,7 @@ describe("SalesforceCRMService", () => {
expect(result).toEqual([]);
expect(mockConnection.query).toHaveBeenCalledWith(
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
@@ -147,7 +159,7 @@ describe("SalesforceCRMService", () => {
// Verify the exact query made
expect(querySpy).toHaveBeenCalledWith(
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
@@ -190,7 +202,7 @@ describe("SalesforceCRMService", () => {
]);
expect(querySpy).toHaveBeenCalledWith(
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test1@example.com','test2@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test1@example.com','test2@example.com')"
);
});
@@ -207,19 +219,16 @@ describe("SalesforceCRMService", () => {
Email: "test@example.com",
// OwnerId: "owner001",
attributes: { type: "Contact" },
Owner: {
Email: "owner@example.com",
},
},
],
};
const ownerQueryResponse = {
records: [],
};
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy
.mockResolvedValueOnce(contactQueryResponse) // Contact query
.mockResolvedValueOnce(ownerQueryResponse); // Owner query
querySpy.mockResolvedValueOnce(contactQueryResponse); // Contact query
const result = await service.getContacts({
emails: "test@example.com",
@@ -231,14 +240,14 @@ describe("SalesforceCRMService", () => {
id: "001",
email: "test@example.com",
ownerId: undefined,
ownerEmail: undefined,
ownerEmail: "owner@example.com",
recordType: "Contact",
},
]);
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
@@ -265,7 +274,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
it("when createEventOn is lead", async () => {
@@ -290,7 +299,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')"
"SELECT Id, Email, OwnerId, Owner.Email FROM Lead WHERE Email IN ('test@example.com')"
);
});
it("when createEventOn is contact under an account", async () => {
@@ -315,7 +324,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
"SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
);
});
});
@@ -328,9 +337,7 @@ describe("SalesforceCRMService", () => {
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy
.mockResolvedValueOnce(contactQueryResponse) // Contact query
.mockResolvedValueOnce(ownerQueryResponse); // Owner query
querySpy.mockResolvedValueOnce(contactQueryResponse); // Contact query
const result = await service.getContacts({
emails: "test@example.com",
@@ -349,11 +356,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
);
expect(querySpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
it("when createEventOn is lead", async () => {
@@ -363,7 +366,7 @@ describe("SalesforceCRMService", () => {
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy.mockResolvedValueOnce(leadQueryResponse).mockResolvedValueOnce(ownerQueryResponse);
querySpy.mockResolvedValueOnce(leadQueryResponse);
const result = await service.getContacts({
emails: "test@example.com",
@@ -382,11 +385,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')"
);
expect(querySpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, Owner.Email FROM Lead WHERE Email IN ('test@example.com')"
);
});
it("when createEventOn is contact under an account", async () => {
@@ -396,9 +395,7 @@ describe("SalesforceCRMService", () => {
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy
.mockResolvedValueOnce(contactUnderAccountQueryResponse)
.mockResolvedValueOnce(ownerQueryResponse);
querySpy.mockResolvedValueOnce(contactUnderAccountQueryResponse);
const result = await service.getContacts({
emails: "test@example.com",
@@ -411,17 +408,13 @@ describe("SalesforceCRMService", () => {
email: "test@example.com",
ownerId: "owner001",
ownerEmail: "owner@example.com",
recordType: "Account",
recordType: "Contact",
},
]);
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
);
expect(querySpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
);
});
});
@@ -435,9 +428,7 @@ describe("SalesforceCRMService", () => {
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy
.mockResolvedValueOnce(contactQueryResponse) // Contact query
.mockResolvedValueOnce(ownerQueryResponse); // Owner query
querySpy.mockResolvedValueOnce(contactQueryResponse); // Contact query
const result = await service.getContacts({
emails: "test@example.com",
@@ -456,11 +447,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')"
);
expect(querySpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, Owner.Email FROM Contact WHERE Email IN ('test@example.com')"
);
});
it("checking against leads", async () => {
@@ -470,7 +457,7 @@ describe("SalesforceCRMService", () => {
// Setup query spy with specific responses
const querySpy = vi.spyOn(mockConnection, "query");
querySpy.mockResolvedValueOnce(leadQueryResponse).mockResolvedValueOnce(ownerQueryResponse);
querySpy.mockResolvedValueOnce(leadQueryResponse);
const result = await service.getContacts({
emails: "test@example.com",
@@ -489,11 +476,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')"
);
expect(querySpy).toHaveBeenNthCalledWith(
2,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, Owner.Email FROM Lead WHERE Email IN ('test@example.com')"
);
});
it("checking against contacts under an account", async () => {
@@ -523,12 +506,7 @@ describe("SalesforceCRMService", () => {
expect(querySpy).toHaveBeenNthCalledWith(
1,
"SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
);
expect(querySpy).toHaveBeenNthCalledWith(2, "SELECT Id, OwnerId FROM Account WHERE Id = 'acc001'");
expect(querySpy).toHaveBeenNthCalledWith(
3,
expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'")
"SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM Contact WHERE Email = 'test@example.com' AND AccountId != null"
);
});
});
+83 -109
View File
@@ -44,9 +44,9 @@ const sfApiErrors = {
};
type ContactRecord = {
Id: string;
Email: string;
OwnerId: string;
Id?: string;
Email?: string;
OwnerId?: string;
[key: string]: any;
};
@@ -348,7 +348,6 @@ export default class SalesforceCRMService implements CRM {
SalesforceRecordEnum.CONTACT;
let soql: string;
let accountOwnerId = "";
log.info(
"Getting contacts for emails",
@@ -366,58 +365,31 @@ export default class SalesforceCRMService implements CRM {
// For an account let's assume that the first email is the one we should be querying against
const attendeeEmail = emailArray[0];
log.info("Searching account for email", safeStringify({ attendeeEmail }));
soql = `SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = '${attendeeEmail}' AND AccountId != null`;
// If this is for a round robin skip then we need to return the account record
if (forRoundRobinSkip) {
const results = await conn.query(soql);
log.info("Account contact search results", safeStringify({ resultCount: results.records.length }));
if (results.records?.length) {
const contact = results.records[0] as { AccountId?: string };
if (contact?.AccountId) {
soql = `SELECT Id, OwnerId FROM Account WHERE Id = '${contact.AccountId}'`;
}
} else {
// If we can't find the exact contact, then we need to search for an account where the contacts share the same email domain
const accountId = await this.getAccountIdBasedOnEmailDomainOfContacts(attendeeEmail);
if (accountId) {
soql = `SELECT Id, OwnerId FROM Account WHERE Id = '${accountId}'`;
}
}
}
// If creating events on contacts or leads
soql = `SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${attendeeEmail}' AND AccountId != null`;
} else {
// Handle Contact/Lead record types
soql = `SELECT Id, Email, OwnerId FROM ${recordToSearch} WHERE Email IN ('${emailArray.join(
soql = `SELECT Id, Email, OwnerId, Owner.Email FROM ${recordToSearch} WHERE Email IN ('${emailArray.join(
"','"
)}')`;
}
const results = await conn.query(soql);
log.info("Query results", safeStringify({ recordCount: results.records?.length }));
// If we're checking against the contact, the ownerId should take precedence
if (recordToSearch === SalesforceRecordEnum.ACCOUNT && results.records?.length) {
const account = results.records[0] as ContactRecord;
if (account?.OwnerId) {
accountOwnerId = account.OwnerId;
}
}
log.info("Query results", safeStringify({ recordCount: results.records?.length }));
let records: ContactRecord[] = [];
// If falling back to contacts, check for the contact before returning the leads or empty array
if (
appOptions.createEventOn === SalesforceRecordEnum.LEAD &&
appOptions.createEventOnLeadCheckForContact
appOptions.createEventOnLeadCheckForContact &&
!forRoundRobinSkip
) {
// Get any matching contacts
const contactSearch = await conn.query(
`SELECT Id, Email, OwnerId FROM ${SalesforceRecordEnum.CONTACT} WHERE Email IN ('${emailArray.join(
"','"
)}')`
`SELECT Id, Email, OwnerId, Owner.Email FROM ${
SalesforceRecordEnum.CONTACT
} WHERE Email IN ('${emailArray.join("','")}')`
);
if (contactSearch?.records?.length > 0) {
@@ -436,84 +408,42 @@ export default class SalesforceCRMService implements CRM {
records = results.records as ContactRecord[];
}
if (recordToSearch === SalesforceRecordEnum.ACCOUNT && forRoundRobinSkip && !results.records.length) {
const attendeeEmail = emailArray[0];
// If we can't find the exact contact, then we need to search for an account where the contacts share the same email domain
const account = await this.getAccountBasedOnEmailDomainOfContacts(attendeeEmail);
if (account) {
records = [account];
}
}
if (!records.length) {
log.info("No records found");
return [];
}
// Handle owner information
if (
const includeOwnerData =
(includeOwner || forRoundRobinSkip) &&
!(await this.shouldSkipAttendeeIfFreeEmailDomain(emailArray[0]))
) {
const ownerIds: Set<string> = new Set();
!(await this.shouldSkipAttendeeIfFreeEmailDomain(emailArray[0]));
if (accountOwnerId) {
ownerIds.add(accountOwnerId);
} else {
records.forEach((record) => {
if (record?.OwnerId) {
ownerIds.add(record.OwnerId);
}
});
}
const includeAccountRecordType = forRoundRobinSkip && recordToSearch === SalesforceRecordEnum.ACCOUNT;
const ownersQuery =
ownerIds.size > 0
? await Promise.all(
Array.from(ownerIds).map(async (ownerId) => {
const result = await this.getSalesforceUserFromUserId(ownerId);
return result;
})
)
: [];
// Filter out any undefined results and ensure records exist
const validOwnersQuery = ownersQuery.filter((query): query is jsforce.QueryResult<ContactRecord> => {
const firstRecord = query?.records?.[0];
if (!firstRecord) return false;
return (
typeof firstRecord === "object" && "Email" in firstRecord && typeof firstRecord.Email === "string"
);
});
return records
.map((record) => {
if (!record?.Id) {
log.warn("Invalid record data", { record });
return null;
}
const ownerEmail = accountOwnerId
? validOwnersQuery[0]?.records[0]?.Email
: validOwnersQuery.find((user) => user.records[0]?.Id === record.OwnerId)?.records[0]?.Email;
return {
id: record.Id,
email: record.Email,
ownerId: record.OwnerId,
ownerEmail,
recordType: accountOwnerId ? SalesforceRecordEnum.ACCOUNT : record.attributes?.type,
};
})
.filter((record): record is NonNullable<typeof record> => record !== null);
}
return records
.map((record) => {
if (!record?.Id || !record?.Email || !record?.attributes?.type) {
log.warn("Invalid record data for basic mapping", { record });
return null;
}
return {
id: record.Id,
email: record.Email,
recordType: record.attributes.type,
};
})
.filter((record): record is NonNullable<typeof record> => record !== null);
return records.map((record) => {
return {
id: includeAccountRecordType ? record?.AccountId : record?.Id || "",
email: record?.Email || "",
recordType: includeAccountRecordType ? SalesforceRecordEnum.ACCOUNT : record?.attributes?.type,
...(includeOwnerData && {
ownerId: record?.OwnerId,
// Handle if Account is nested
ownerEmail:
recordToSearch === SalesforceRecordEnum.ACCOUNT &&
record?.attributes?.type !== SalesforceRecordEnum.ACCOUNT
? record?.Account?.Owner?.Email
: record?.Owner?.Email,
}),
};
});
} catch (error) {
log.error("Error in getContacts", safeStringify({ error }));
return [];
@@ -917,6 +847,50 @@ export default class SalesforceCRMService implements CRM {
return this.getDominantAccountId(response.records as { AccountId: string }[]);
}
private async getAccountBasedOnEmailDomainOfContacts(email: string) {
const conn = await this.conn;
const emailDomain = email.split("@")[1];
// First check if an account has the same website as the email domain of the attendee
const accountQuery = await conn.query(
`SELECT Id, OwnerId, Owner.Email FROM Account WHERE Website LIKE '%${emailDomain}%'`
);
if (accountQuery.records.length > 0) {
return {
...(accountQuery.records[0] as { Id?: string; OwnerId?: string; Owner?: { Email?: string } }),
Email: undefined,
};
}
// Fallback to querying which account the majority of contacts are under
const contactQuery = await conn.query(
`SELECT Id, Email, AccountId, Account.OwnerId, Account.Owner.Email FROM Contact WHERE Email LIKE '%@${emailDomain}' AND AccountId != null`
);
const contacts = contactQuery?.records as {
AccountId: string;
Account: { OwnerId?: string; Owner: { Email: string } };
}[];
if (!contacts) return;
const dominantAccountId = this.getDominantAccountId(contacts);
const contactUnderAccount = contacts.find((contact) => contact.AccountId === dominantAccountId);
return {
Id: dominantAccountId,
Email: undefined,
OwnerId: contactUnderAccount?.Account?.OwnerId,
Owner: {
Email: contactUnderAccount?.Account?.Owner?.Email,
},
attributes: {
type: SalesforceRecordEnum.ACCOUNT,
},
};
}
private setFallbackToContact(boolean: boolean) {
this.fallbackToContact = boolean;
}