import { Client, cacheExchange, fetchExchange } from "@urql/core"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { Contact } from "@calcom/types/CrmService"; import { SalesforceRecordEnum } from "../enums"; import getAllPossibleWebsiteValuesFromEmailDomain from "../utils/getAllPossibleWebsiteValuesFromEmailDomain"; import getDominantAccountId from "../utils/getDominantAccountId"; import { GetAccountRecordsForRRSkip } from "./documents/queries"; export class SalesforceGraphQLClient { private log: typeof logger; private version = "v63.0"; private accessToken: string; private client: Client; constructor({ accessToken, instanceUrl }: { accessToken: string; instanceUrl: string }) { this.accessToken = accessToken; this.client = new Client({ url: `${instanceUrl}/services/data/${this.version}/graphql`, exchanges: [cacheExchange, fetchExchange], fetchOptions: () => { return { headers: { authorization: `Bearer ${this.accessToken}` }, "Content-Type": "application/json", }; }, }); this.log = logger.getSubLogger({ prefix: ["[SalesforceGraphQLClient]"] }); } /** * Returns the owner of an account. There are three methods we use to the find the account owner * 1. If there is a contact with that matches the email, return the account owner * 2. If no contact is found, then find an account that is an exact match of the email domain * 3. If no account is found, then find contacts that match the email domain and find the account that the majority of contacts are connect to */ async GetAccountRecordsForRRSkip(email: string): Promise { const log = logger.getSubLogger({ prefix: [`[getAccountRecordsForRRSkip]:${email}`] }); const emailDomain = email.split("@")[1]; const websites = this.getAllPossibleAccountWebsiteFromEmailDomain(emailDomain); log.info(`Query against email and email domain of ${emailDomain}`); const query = await this.client.query(GetAccountRecordsForRRSkip, { email, websites, emailDomain: `%@${emailDomain}`, }); const queryData = query?.data; if (query?.error) { const errors = query.error; if (errors.graphQLErrors.length) { log.error("GraphQL error", safeStringify({ errors })); } if (errors.networkError) { log.error("Network error", safeStringify({ errors })); } } if (!queryData) { log.error("No query data found", safeStringify({ error: query?.error })); return []; } // If there is an existing contact, return the owner of the account if (queryData.uiapi.query.Contact) { const contact = queryData.uiapi.query.Contact?.edges?.[0]?.node; if (contact) { log.info(`Existing contact found with id ${contact.Id}`); return [ { id: contact.AccountId?.value || contact.Id, email: contact.Email?.value, ownerId: contact.Account?.Owner?.Id, ownerEmail: contact.Account?.Owner?.Email?.value, recordType: SalesforceRecordEnum.ACCOUNT, }, ]; } } // If no contact is found, query for accounts based on website field if (queryData.uiapi.query.Account) { const account = queryData.uiapi.query.Account?.edges?.[0]?.node; if (account) { log.info( `Existing account with website that matches email domain of ${emailDomain} found with id ${account.Id}` ); return [ { id: account.Id, email: "", ownerId: account.Owner?.Id, ownerEmail: account.Owner?.Email?.value, recordType: SalesforceRecordEnum.ACCOUNT, }, ]; } } // If no account is found, find an account based on existing contacts if (queryData.uiapi.query.relatedContacts) { const relatedContactsResults = queryData.uiapi.query.relatedContacts?.edges; if (!relatedContactsResults) return []; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - in CD/CI pipeline this will have any type const relatedContacts = relatedContactsResults.reduce((contacts, edge) => { const node = edge?.node; if (!node) { log.error("A related contact query didn't include a node"); return contacts; } if (!node.AccountId?.value) { log.error(`A related contact with id ${node.Id} didn't have an account id`); return contacts; } if (!node.Account?.Owner?.Id) { log.error(`A related contact with id ${node.Id} didn't have an account owner id`); return contacts; } if (!node.Account?.Owner?.Email?.value) { log.error(`A related contact with id ${node.Id} didn't have an account owner email`); return contacts; } contacts.push({ id: node.Id, AccountId: node.AccountId.value, ownerId: node.Account.Owner.Id, ownerEmail: node.Account.Owner.Email.value, }); return contacts; }, [] as { id: string; AccountId: string; ownerId: string; ownerEmail: string }[]); const dominantAccountId = getDominantAccountId(relatedContacts); if (!dominantAccountId) { log.error( "Could not find dominant account id with the following contacts", safeStringify({ relatedContacts }) ); return []; } // Get a contact from the dominant account // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - in CD/CI pipeline this will have any type const contactUnderAccount = relatedContacts.find((contact) => contact.AccountId === dominantAccountId); if (!contactUnderAccount) { log.error( `Could not find a contact under the dominant account id ${dominantAccountId}`, safeStringify({ relatedContacts }) ); return []; } log.info(`Account found via related contacts with account id ${dominantAccountId}`); return [ { id: dominantAccountId, email: "", ownerId: contactUnderAccount.ownerId, ownerEmail: contactUnderAccount.ownerEmail, recordType: SalesforceRecordEnum.ACCOUNT, }, ]; } log.info("No account found for attendee"); return []; } private getAllPossibleAccountWebsiteFromEmailDomain(emailDomain: string) { return getAllPossibleWebsiteValuesFromEmailDomain(emailDomain); } }