diff --git a/packages/destination-actions/src/destinations/actions-pardot/pa-operations.ts b/packages/destination-actions/src/destinations/actions-pardot/pa-operations.ts index d7110c05fe..e1e5e60720 100644 --- a/packages/destination-actions/src/destinations/actions-pardot/pa-operations.ts +++ b/packages/destination-actions/src/destinations/actions-pardot/pa-operations.ts @@ -1,4 +1,4 @@ -import { RequestClient } from '@segment/actions-core' +import { RequestClient, StatsContext } from '@segment/actions-core' import type { Payload as ProspectsPayload } from './prospects/generated-types' import { ProspectsType } from './pa-type' @@ -20,9 +20,10 @@ export default class Pardot { this.request = request } - upsertRecord = async (payload: ProspectsPayload) => { + upsertRecord = async (payload: ProspectsPayload, statsContext?: StatsContext) => { const prospect = this.buildProspectJSON(payload) + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:upsert-prospect-record`]) return this.request( `${this.baseUrl}/api/${PARDOT_API_VERSION}/objects/prospects/do/upsertLatestByEmail`, { diff --git a/packages/destination-actions/src/destinations/actions-pardot/prospects/index.ts b/packages/destination-actions/src/destinations/actions-pardot/prospects/index.ts index ea889759b9..c3ad8a1877 100644 --- a/packages/destination-actions/src/destinations/actions-pardot/prospects/index.ts +++ b/packages/destination-actions/src/destinations/actions-pardot/prospects/index.ts @@ -181,11 +181,11 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const baseUrl = settings.isSandbox ? 'https://pi.demo.pardot.com' : 'https://pi.pardot.com' const pa: Pardot = new Pardot(settings.businessUnitID, baseUrl, request) try { - return await pa.upsertRecord(payload) + return await pa.upsertRecord(payload, statsContext) } catch (err) { const error = err as HTTPError if (!error.response) { diff --git a/packages/destination-actions/src/destinations/amazon-amc/index.ts b/packages/destination-actions/src/destinations/amazon-amc/index.ts index ff0244a582..791eb7052e 100644 --- a/packages/destination-actions/src/destinations/amazon-amc/index.ts +++ b/packages/destination-actions/src/destinations/amazon-amc/index.ts @@ -137,7 +137,7 @@ const destination: AudienceDestinationDefinition = { full_audience_sync: false // If true, we send the entire audience. If false, we just send the delta. }, async createAudience(request, createAudienceInput) { - const { audienceName, audienceSettings, settings } = createAudienceInput + const { audienceName, audienceSettings, settings, statsContext } = createAudienceInput const endpoint = settings.region const description = audienceSettings?.description const advertiser_id = audienceSettings?.advertiserId @@ -206,7 +206,7 @@ const destination: AudienceDestinationDefinition = { // Regular expression to find a advertiserId numeric string and replace the quoted advertiserId string with an unquoted number // AdvertiserId is very big number string and can not be assigned or converted to number directly as it changes the value due to integer overflow. payloadString = payloadString.replace(REGEX_ADVERTISERID, '"advertiserId":$1') - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:createAudience`]) const response = await request(`${endpoint}/amc/audiences/metadata`, { method: 'POST', body: payloadString, @@ -227,7 +227,7 @@ const destination: AudienceDestinationDefinition = { async getAudience(request, getAudienceInput) { // getAudienceInput.externalId represents audience ID that was created in createAudience const audience_id = getAudienceInput.externalId - const { settings } = getAudienceInput + const { settings, statsContext } = getAudienceInput const endpoint = settings.region if (!audience_id) { @@ -236,6 +236,7 @@ const destination: AudienceDestinationDefinition = { // @ts-ignore - TS doesn't know about the oauth property const authSettings = getAuthSettings(settings) const authToken = await getAuthToken(request, settings, authSettings) + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:getAudience`]) const response = await request(`${endpoint}/amc/audiences/metadata/${audience_id}`, { method: 'GET', headers: { diff --git a/packages/destination-actions/src/destinations/amazon-amc/syncAudiencesToDSP/index.ts b/packages/destination-actions/src/destinations/amazon-amc/syncAudiencesToDSP/index.ts index f5297861b5..dce8911687 100644 --- a/packages/destination-actions/src/destinations/amazon-amc/syncAudiencesToDSP/index.ts +++ b/packages/destination-actions/src/destinations/amazon-amc/syncAudiencesToDSP/index.ts @@ -1,4 +1,4 @@ -import type { ActionDefinition, RequestClient } from '@segment/actions-core' +import type { ActionDefinition, RequestClient, StatsContext } from '@segment/actions-core' import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' import { CONSTANTS, RecordsResponseType } from '../utils' @@ -109,11 +109,11 @@ const action: ActionDefinition = { default: 10000 } }, - perform: (request, { settings, payload, audienceSettings }) => { - return processPayload(request, settings, [payload], audienceSettings) + perform: (request, { settings, payload, audienceSettings, statsContext }) => { + return processPayload(request, settings, [payload], audienceSettings, statsContext) }, - performBatch: (request, { settings, payload: payloads, audienceSettings }) => { - return processPayload(request, settings, payloads, audienceSettings) + performBatch: (request, { settings, payload: payloads, audienceSettings, statsContext }) => { + return processPayload(request, settings, payloads, audienceSettings, statsContext) } } @@ -121,11 +121,13 @@ async function processPayload( request: RequestClient, settings: Settings, payload: Payload[], - audienceSettings: AudienceSettings + audienceSettings: AudienceSettings, + statsContext: StatsContext | undefined ) { const payloadRecord = createPayloadToUploadRecords(payload, audienceSettings) // Regular expression to find a audienceId numeric string and replace the quoted audienceId string with an unquoted number const payloadString = JSON.stringify(payloadRecord).replace(/"audienceId":"(\d+)"/, '"audienceId":$1') + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:uploadAudienceRecords`]) const response = await request(`${settings.region}/amc/audiences/records`, { method: 'POST', diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/postConversion/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/postConversion/index.ts index 90ccf38c06..20c6751e45 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/postConversion/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/postConversion/index.ts @@ -209,7 +209,7 @@ const action: ActionDefinition = { } }, - perform: async (request, { payload, settings }) => { + perform: async (request, { payload, settings, statsContext }) => { /* Enforcing this here since Conversion ID is required for the Enhanced Conversions API but not for the Google Ads API. */ if (!settings.conversionTrackingId) { @@ -251,6 +251,8 @@ const action: ActionDefinition = { }) try { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:postConversion`]) + return await request('https://www.google.com/ads/event/api/v1', { method: 'post', searchParams: { diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadCallConversion/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadCallConversion/index.ts index a40b4cfad2..742f8b6098 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadCallConversion/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadCallConversion/index.ts @@ -145,6 +145,7 @@ const action: ActionDefinition = { customVariableIds.data[0].results ) } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:uploadCallConversions`]) const response: ModifiedResponse = await request( `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${ settings.customerId diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts index 06341a0ce3..47e3016a0d 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts @@ -311,6 +311,7 @@ const action: ActionDefinition = { hashedPhoneNumber: isHashedInformation(payload.phone_number) ? payload.phone_number : hash(phoneNumber) } as UserIdentifierInterface) } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:uploadClickConversions`]) const response: ModifiedResponse = await request( `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${ diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts index 638a2b092b..647d57422d 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts @@ -309,6 +309,10 @@ const action: ActionDefinition = { } }) } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:uploadConversionAdjustments` + ]) const response: ModifiedResponse = await request( `https://googleads.googleapis.com/${getApiVersion(features, statsContext)}/customers/${ diff --git a/packages/destination-actions/src/destinations/google-sheets/postSheet/index.ts b/packages/destination-actions/src/destinations/google-sheets/postSheet/index.ts index 63757f3072..b00d0f9357 100644 --- a/packages/destination-actions/src/destinations/google-sheets/postSheet/index.ts +++ b/packages/destination-actions/src/destinations/google-sheets/postSheet/index.ts @@ -77,11 +77,11 @@ const action: ActionDefinition = { default: true } }, - perform: (request, { payload }) => { - return processData(request, [payload]) + perform: (request, { payload, statsContext }) => { + return processData(request, [payload], statsContext) }, - performBatch: (request, { payload }) => { - return processData(request, payload) + performBatch: (request, { payload, statsContext }) => { + return processData(request, payload, statsContext) } } diff --git a/packages/destination-actions/src/destinations/google-sheets/postSheet/operations.ts b/packages/destination-actions/src/destinations/google-sheets/postSheet/operations.ts index 0b4a33ce23..9b282b37a6 100644 --- a/packages/destination-actions/src/destinations/google-sheets/postSheet/operations.ts +++ b/packages/destination-actions/src/destinations/google-sheets/postSheet/operations.ts @@ -1,5 +1,5 @@ import type { Payload } from './generated-types' -import { IntegrationError, RequestClient } from '@segment/actions-core' +import { IntegrationError, RequestClient, StatsContext } from '@segment/actions-core' import { GoogleSheets, GetResponse } from '../googleapis/index' import { CONSTANTS } from '../constants' @@ -119,7 +119,12 @@ function processGetSpreadsheetResponse(response: GetResponse, events: Payload[], * @param updateBatch array of events to commit to the spreadsheet * @param gs interface object capable of interacting with Google Sheets API */ -async function processUpdateBatch(mappingSettings: MappingSettings, updateBatch: UpdateBatch[], gs: GoogleSheets) { +async function processUpdateBatch( + mappingSettings: MappingSettings, + updateBatch: UpdateBatch[], + gs: GoogleSheets, + statsContext: StatsContext | undefined +) { // Utility function used to calculate which range an event should be written to const getRange = (targetIndex: number, columnCount: number) => { const targetRange = new A1(1, targetIndex) @@ -142,7 +147,7 @@ async function processUpdateBatch(mappingSettings: MappingSettings, updateBatch: range: `${mappingSettings.spreadsheetName}!${headerRowRange.toString()}`, values: [['id', ...mappingSettings.columns]] }) - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:batchUpdate`]) return gs.batchUpdate(mappingSettings, batchPayload) } @@ -186,7 +191,12 @@ async function processUpdateBatch(mappingSettings: MappingSettings, updateBatch: * @param gs interface object capable of interacting with Google Sheets API * @returns */ -async function processAppendBatch(mappingSettings: MappingSettings, appendBatch: AppendBatch[], gs: GoogleSheets) { +async function processAppendBatch( + mappingSettings: MappingSettings, + appendBatch: AppendBatch[], + gs: GoogleSheets, + statsContext: StatsContext | undefined +) { if (appendBatch.length <= 0) { return } @@ -195,7 +205,7 @@ async function processAppendBatch(mappingSettings: MappingSettings, appendBatch: const values = appendBatch.map(({ identifier, event }) => generateColumnValuesFromFields(identifier, event, mappingSettings.columns) ) - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:append-spreadsheet`]) return gs.append(mappingSettings, `A${DATA_ROW_OFFSET}`, values) } @@ -204,7 +214,7 @@ async function processAppendBatch(mappingSettings: MappingSettings, appendBatch: * @param request request object used to perform HTTP calls * @param events array of events to commit to the spreadsheet */ -async function processData(request: RequestClient, events: Payload[]) { +async function processData(request: RequestClient, events: Payload[], statsContext: StatsContext | undefined) { // These are assumed to be constant across all events const mappingSettings = { spreadsheetId: events[0].spreadsheet_id, @@ -214,7 +224,7 @@ async function processData(request: RequestClient, events: Payload[]) { } const gs: GoogleSheets = new GoogleSheets(request) - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:get-spreadsheet`]) // Get all of the row identifiers (assumed to be in the first column A) const response = await gs.get(mappingSettings, `A${DATA_ROW_OFFSET}:A`) @@ -222,8 +232,8 @@ async function processData(request: RequestClient, events: Payload[]) { const { appendBatch, updateBatch } = processGetSpreadsheetResponse(response.data, events, mappingSettings) const promises = [ - processUpdateBatch(mappingSettings, updateBatch, gs), - processAppendBatch(mappingSettings, appendBatch, gs) + processUpdateBatch(mappingSettings, updateBatch, gs, statsContext), + processAppendBatch(mappingSettings, appendBatch, gs, statsContext) ] return await Promise.all(promises) diff --git a/packages/destination-actions/src/destinations/hubspot/api/index.ts b/packages/destination-actions/src/destinations/hubspot/api/index.ts index 391e6d6840..b560ef6923 100644 --- a/packages/destination-actions/src/destinations/hubspot/api/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/api/index.ts @@ -1,4 +1,4 @@ -import { HTTPError } from '@segment/actions-core' +import { HTTPError, StatsContext } from '@segment/actions-core' import { ModifiedResponse } from '@segment/actions-core' import { RequestClient } from '@segment/actions-core' import { CustomSearchToAssociateThrowableError } from '../errors' @@ -35,7 +35,8 @@ export class Hubspot { searchFields: { [key: string]: unknown }, objectType: string, responseProperties: string[], - responseSortBy: string[] + responseSortBy: string[], + statsContext?: StatsContext ) { if (typeof searchFields === 'object' && Object.keys(searchFields).length > 0) { const searchPayload: SearchPayload = { @@ -54,6 +55,10 @@ export class Hubspot { ] }) } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:search-${this.objectType}-object` + ]) return this.request(`${HUBSPOT_BASE_URL}/crm/v3/objects/${objectType}/search`, { method: 'POST', @@ -70,7 +75,16 @@ export class Hubspot { * @param {{[key: string]: unknown}} properties A list of key-value pairs of properties of the object * @returns {Promise>} A promise that resolves the updated object */ - async create(properties: { [key: string]: unknown }, associations: CreateAssociation[] = []) { + async create( + properties: { [key: string]: unknown }, + associations: CreateAssociation[] = [], + statsContext?: StatsContext + ) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:create-${this.objectType}-object` + ]) + return this.request(`${HUBSPOT_BASE_URL}/crm/v3/objects/${this.objectType}`, { method: 'POST', json: { @@ -87,7 +101,17 @@ export class Hubspot { * @param {String} [idProperty] Unique property of object record to match with uniqueIdentifier, if this parameter is not defined then uniqueIdentifier is matched with HubSpot generated record ID * @returns {Promise>} A promise that resolves the updated object */ - async update(uniqueIdentifier: string, properties: { [key: string]: unknown }, idProperty?: string) { + async update( + uniqueIdentifier: string, + properties: { [key: string]: unknown }, + idProperty?: string, + statsContext?: StatsContext + ) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:update-${this.objectType}-object` + ]) + // Construct the URL to update record of given objectType // URL to update record by ID: /crm/v3/objects/{objectType}/{objectId} // URL to update record by unique property: /crm/v3/objects/{objectType}/{uniqueIdentifier}?idProperty={uniquePropertyInternalName} @@ -110,7 +134,8 @@ export class Hubspot { * @param {batchInput[]} input Unique property of object record to match with uniqueIdentifier, if this parameter is not defined then uniqueIdentifier is matched with HubSpot generated record ID * @returns {Promise>} A promise that resolves the updated object */ - async associate(objectId: string, toObjectId: string, associations: AssociationType[]) { + async associate(objectId: string, toObjectId: string, associations: AssociationType[], statsContext?: StatsContext) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:associate-objects`]) const associateURL = `${HUBSPOT_BASE_URL}/crm/v4/objects/${this.objectType}/${objectId}/associations/${this.toObjectType}/${toObjectId}` return this.request(associateURL, { @@ -121,7 +146,8 @@ export class Hubspot { async getObjectResponseToAssociate( searchFieldsToAssociateCustomObjects: { [key: string]: unknown } | undefined, - associationType: AssociationType | null + associationType: AssociationType | null, + statsContext?: StatsContext ) { try { if ( @@ -136,7 +162,8 @@ export class Hubspot { { ...searchFieldsToAssociateCustomObjects }, this.toObjectType, [], - [] + [], + statsContext ) if (searchCustomResponseToAssociate?.data && searchCustomResponseToAssociate?.data?.total) { return searchCustomResponseToAssociate diff --git a/packages/destination-actions/src/destinations/hubspot/sendCustomBehavioralEvent/index.ts b/packages/destination-actions/src/destinations/hubspot/sendCustomBehavioralEvent/index.ts index c67a3f1bc1..b8a07b4ab9 100644 --- a/packages/destination-actions/src/destinations/hubspot/sendCustomBehavioralEvent/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/sendCustomBehavioralEvent/index.ts @@ -93,7 +93,7 @@ const action: ActionDefinition = { } } }, - perform: (request, { payload, settings }) => { + perform: (request, { payload, settings, statsContext }) => { const eventName = transformEventName(payload.eventName) const event: CustomBehavioralEvent = { @@ -118,7 +118,10 @@ const action: ActionDefinition = { if (!payload.utk && !payload.email && !payload.objectId) { throw new PayloadValidationError(`One of the following parameters: email, user token, or objectId is required`) } - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:send-custom-behavioural-event` + ]) return request(`${HUBSPOT_BASE_URL}/events/v3/send`, { method: 'post', json: event diff --git a/packages/destination-actions/src/destinations/hubspot/upsertCompany/index.ts b/packages/destination-actions/src/destinations/hubspot/upsertCompany/index.ts index 56f80cfa9e..a5ff667d73 100644 --- a/packages/destination-actions/src/destinations/hubspot/upsertCompany/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/upsertCompany/index.ts @@ -210,7 +210,7 @@ const action: ActionDefinition = { allowNull: false } }, - perform: async (request, { payload, transactionContext }) => { + perform: async (request, { payload, transactionContext, statsContext }) => { // Check if user has mapped the internal property SEGMENT_UNIQUE_IDENTIFIER in other Properties field if (payload.properties?.[SEGMENT_UNIQUE_IDENTIFIER]) { throw RestrictedPropertyThrowableError @@ -248,7 +248,8 @@ const action: ActionDefinition = { const updateCompanyResponse = await hubspotApiClient.update( payload.groupid, companyProperties, - SEGMENT_UNIQUE_IDENTIFIER + SEGMENT_UNIQUE_IDENTIFIER, + statsContext ) companyId = updateCompanyResponse.data.id @@ -287,7 +288,8 @@ const action: ActionDefinition = { { ...payload.companysearchfields }, 'companies', responseProperties, - responseSortBy + responseSortBy, + statsContext ) } catch (e) { // HubSpot throws a generic 400 error if an undefined property is used in search @@ -311,7 +313,7 @@ const action: ActionDefinition = { // Create a wrapper function which calls createCompany and returns the response const createCompanyWrapper = async function () { - return await hubspotApiClient.create(companyProperties) + return await hubspotApiClient.create(companyProperties, [], statsContext) } companyId = await upsertCompanyWithRetry(request, createCompanyWrapper) @@ -326,7 +328,7 @@ const action: ActionDefinition = { // Create a wrapper function which calls updateCompany and returns the response const updateCompanyWrapper = async function () { - return await hubspotApiClient.update(companyId, companyProperties) + return await hubspotApiClient.update(companyId, companyProperties, undefined, statsContext) } await upsertCompanyWithRetry(request, updateCompanyWrapper) @@ -335,6 +337,7 @@ const action: ActionDefinition = { // Associate company with contact if Associate Contact flag is set to true if (payload.associateContact && transactionContext?.transaction?.contact_id) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:associate-objects`]) await associateCompanyToContact(request, companyId, transactionContext.transaction.contact_id, ASSOCIATION_TYPE) } } diff --git a/packages/destination-actions/src/destinations/hubspot/upsertContact/index.ts b/packages/destination-actions/src/destinations/hubspot/upsertContact/index.ts index 138c1cc695..c15c8654b4 100644 --- a/packages/destination-actions/src/destinations/hubspot/upsertContact/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/upsertContact/index.ts @@ -1,4 +1,4 @@ -import { HTTPError } from '@segment/actions-core' +import { HTTPError, StatsContext } from '@segment/actions-core' import { ActionDefinition, RequestClient, IntegrationError } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' @@ -193,7 +193,7 @@ const action: ActionDefinition = { default: false } }, - perform: async (request, { payload, transactionContext }) => { + perform: async (request, { payload, transactionContext, statsContext }) => { const contactProperties = { company: payload.company, firstname: payload.firstname, @@ -216,7 +216,7 @@ const action: ActionDefinition = { */ try { - const response = await updateContact(request, payload.email, contactProperties) + const response = await updateContact(request, payload.email, contactProperties, statsContext) // cache contact_id for it to be available for company action transactionContext?.setTransaction('contact_id', response.data.id) @@ -231,14 +231,15 @@ const action: ActionDefinition = { const hasLCSChanged = currentLCS === payload.lifecyclestage.toLowerCase() if (hasLCSChanged) return response // reset lifecycle stage - await updateContact(request, payload.email, { lifecyclestage: '' }) + await updateContact(request, payload.email, { lifecyclestage: '' }, statsContext) + // update contact again with new lifecycle stage - return updateContact(request, payload.email, contactProperties) + return updateContact(request, payload.email, contactProperties, statsContext) } return response } catch (ex) { if ((ex as HTTPError)?.response?.status == 404) { - const result = await createContact(request, contactProperties) + const result = await createContact(request, contactProperties, statsContext) // cache contact_id for it to be available for company action transactionContext?.setTransaction('contact_id', result.data.id) @@ -287,7 +288,12 @@ const action: ActionDefinition = { } } -async function createContact(request: RequestClient, contactProperties: ContactProperties) { +async function createContact( + request: RequestClient, + contactProperties: ContactProperties, + statsContext?: StatsContext +) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:create-contact-object`]) return request(`${HUBSPOT_BASE_URL}/crm/v3/objects/contacts`, { method: 'POST', json: { @@ -296,7 +302,13 @@ async function createContact(request: RequestClient, contactProperties: ContactP }) } -async function updateContact(request: RequestClient, email: string, properties: ContactProperties) { +async function updateContact( + request: RequestClient, + email: string, + properties: ContactProperties, + statsContext?: StatsContext +) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:update-contact-object`]) return request(`${HUBSPOT_BASE_URL}/crm/v3/objects/contacts/${email}?idProperty=email`, { method: 'PATCH', json: { diff --git a/packages/destination-actions/src/destinations/hubspot/upsertCustomObjectRecord/index.ts b/packages/destination-actions/src/destinations/hubspot/upsertCustomObjectRecord/index.ts index da9c259a41..34d8f15afe 100644 --- a/packages/destination-actions/src/destinations/hubspot/upsertCustomObjectRecord/index.ts +++ b/packages/destination-actions/src/destinations/hubspot/upsertCustomObjectRecord/index.ts @@ -108,7 +108,7 @@ const action: ActionDefinition = { return getAssociationLabel(request, payload) } }, - perform: async (request, { payload }) => { + perform: async (request, { payload, statsContext }) => { // Attempt to search Custom Object record with Custom Search Fields // If Custom Search Fields doesn't have any defined property, skip the search and assume record was not found let searchCustomResponse: ModifiedResponse | null = null @@ -122,7 +122,8 @@ const action: ActionDefinition = { { ...payload.customObjectSearchFields }, payload.objectType, [], - [] + [], + statsContext ) } catch (e) { // HubSpot throws a generic 400 error if an undefined property is used in search @@ -140,7 +141,8 @@ const action: ActionDefinition = { // Get Custom object response on the basis of provided search fields to associate searchCustomResponseToAssociate = await hubspotApiClient.getObjectResponseToAssociate( payload.searchFieldsToAssociateCustomObjects, - parsedAssociationType + parsedAssociationType, + statsContext ) // if it gives single unique record then associate else skip it for now const toCustomObjectId = @@ -162,22 +164,36 @@ const action: ActionDefinition = { if (!createNewCustomRecord) { return 'There was no record found to update. If you want to create a new custom object record in such cases, enable the Create Custom Object Record if Not Found flag' } - upsertCustomRecordResponse = await hubspotApiClient.create(properties, association ? [association] : []) + upsertCustomRecordResponse = await hubspotApiClient.create( + properties, + association ? [association] : [], + statsContext + ) } else { // Throw error if more than one custom object record were found with search criteria if (searchCustomResponse?.data?.total > 1) { throw MultipleCustomRecordsInSearchResultThrowableError } // An existing Custom object record was identified, attempt to update the same record - upsertCustomRecordResponse = await hubspotApiClient.update(searchCustomResponse.data.results[0].id, properties) + upsertCustomRecordResponse = await hubspotApiClient.update( + searchCustomResponse.data.results[0].id, + properties, + undefined, + statsContext + ) // If we have custom object record id to associate then associate it else don't associate if (toCustomObjectId && parsedAssociationType) { - await hubspotApiClient.associate(searchCustomResponse.data.results[0].id, toCustomObjectId, [ - { - associationCategory: parsedAssociationType.associationCategory, - associationTypeId: parsedAssociationType.associationTypeId - } - ]) + await hubspotApiClient.associate( + searchCustomResponse.data.results[0].id, + toCustomObjectId, + [ + { + associationCategory: parsedAssociationType.associationCategory, + associationTypeId: parsedAssociationType.associationTypeId + } + ], + statsContext + ) } } // If Provided Custom Search Fields to associate gives a multiple record , then throw an error! diff --git a/packages/destination-actions/src/destinations/intercom/groupIdentifyContact/index.ts b/packages/destination-actions/src/destinations/intercom/groupIdentifyContact/index.ts index be0e0e0b08..f52ccad263 100644 --- a/packages/destination-actions/src/destinations/intercom/groupIdentifyContact/index.ts +++ b/packages/destination-actions/src/destinations/intercom/groupIdentifyContact/index.ts @@ -116,15 +116,22 @@ const action: ActionDefinition = { * * Note: Companies will be only visible in Intercom's Dashboard when there is at least one associated contact. */ - perform: async (request, { payload }) => { + perform: async (request, { payload, statsContext }) => { const contact = await getUniqueIntercomContact(request, payload) delete payload.email delete payload.external_id - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:create-update-intercom-company` + ]) const response = await createOrUpdateIntercomCompany(request, payload) if (contact) { payload.contact_id = contact.id const companyId = response.data.id + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:associate-contact-to-intercom-company` + ]) return attachContactToIntercomCompany(request, contact.id, companyId) } return response diff --git a/packages/destination-actions/src/destinations/intercom/identifyContact/index.ts b/packages/destination-actions/src/destinations/intercom/identifyContact/index.ts index fafa3bd759..95f9295378 100644 --- a/packages/destination-actions/src/destinations/intercom/identifyContact/index.ts +++ b/packages/destination-actions/src/destinations/intercom/identifyContact/index.ts @@ -92,7 +92,7 @@ const action: ActionDefinition = { defaultObjectUI: 'keyvalue' } }, - perform: async (request, { payload }) => { + perform: async (request, { payload, statsContext }) => { /** * Searches for a contact with the given payload. * If unique user is found, updates the contact first. @@ -107,8 +107,16 @@ const action: ActionDefinition = { try { const contact = await getUniqueIntercomContact(request, payload) if (contact) { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:update-intercom-contact` + ]) return updateIntercomContact(request, contact.id, payload) } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:create-intercom-contact` + ]) return await createIntercomContact(request, payload) } catch (error) { if (error?.response?.status === 409) { diff --git a/packages/destination-actions/src/destinations/intercom/trackEvent/index.ts b/packages/destination-actions/src/destinations/intercom/trackEvent/index.ts index a67d892b34..cf0efb2b17 100644 --- a/packages/destination-actions/src/destinations/intercom/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/intercom/trackEvent/index.ts @@ -81,13 +81,13 @@ const action: ActionDefinition = { } } }, - perform: async (request, { payload }) => { + perform: async (request, { payload, statsContext }) => { payload.created_at = convertValidTimestamp(payload.created_at) delete payload.metadata?.email // If only an email is passed, then this might be a lead - so retrieve the contact if (payload.email && !payload.user_id && !payload.id) { - const contact = await getUniqueIntercomContact(request, payload) + const contact = await getUniqueIntercomContact(request, payload, statsContext) if (contact) { payload.id = contact.id } else { @@ -96,6 +96,7 @@ const action: ActionDefinition = { } possiblyPopulatePrice(payload) + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:track-events`]) return request('https://api.intercom.io/events', { method: 'POST', diff --git a/packages/destination-actions/src/destinations/intercom/util.ts b/packages/destination-actions/src/destinations/intercom/util.ts index 551af35bd5..a67ddbe168 100644 --- a/packages/destination-actions/src/destinations/intercom/util.ts +++ b/packages/destination-actions/src/destinations/intercom/util.ts @@ -1,4 +1,4 @@ -import { ModifiedResponse, RequestClient } from '@segment/actions-core' +import { ModifiedResponse, RequestClient, StatsContext } from '@segment/actions-core' import dayjs from '../../lib/dayjs' interface IntercomContact { @@ -20,7 +20,11 @@ interface SearchPayload { * * Intercom's API Docs - https://developers.intercom.com/intercom-api-reference/reference/search-for-contacts */ -export async function getUniqueIntercomContact(request: RequestClient, payload: SearchPayload) { +export async function getUniqueIntercomContact( + request: RequestClient, + payload: SearchPayload, + statsContext?: StatsContext +) { const { external_id, email } = payload let query if (external_id) { @@ -38,6 +42,7 @@ export async function getUniqueIntercomContact(request: RequestClient, payload: } else { return } + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:search-intercom-contact`]) const response: ModifiedResponse = await request('https://api.intercom.io/contacts/search', { method: 'POST', diff --git a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/apiEvent/index.ts b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/apiEvent/index.ts index f8da008bfd..1e2c20105e 100644 --- a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/apiEvent/index.ts +++ b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/apiEvent/index.ts @@ -11,7 +11,8 @@ const action: ActionDefinition = { contactKey: contactKeyAPIEvent, data: eventData }, - perform: (request, { settings, payload }) => { + perform: (request, { settings, payload, statsContext }) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:apiEvent`]) return request(`https://${settings.subdomain}.rest.marketingcloudapis.com/interaction/v1/events`, { method: 'POST', json: payload diff --git a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contact/index.ts b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contact/index.ts index 8f7283b6ff..fe04342b64 100644 --- a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contact/index.ts +++ b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contact/index.ts @@ -10,8 +10,9 @@ const action: ActionDefinition = { fields: { contactKey: { ...contactKey, required: true } }, - perform: (request, { settings, payload }) => { -return request(`https://${settings.subdomain}.rest.marketingcloudapis.com/contacts/v1/contacts`, { + perform: (request, { settings, payload, statsContext }) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:create-contact`]) + return request(`https://${settings.subdomain}.rest.marketingcloudapis.com/contacts/v1/contacts`, { method: 'POST', json: { contactKey: payload.contactKey, diff --git a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contactDataExtension/index.ts b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contactDataExtension/index.ts index d2b965ddf2..2ba73ecfed 100644 --- a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contactDataExtension/index.ts +++ b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/contactDataExtension/index.ts @@ -30,11 +30,11 @@ const action: ActionDefinition = { enable_batching: enable_batching, batch_size: batch_size }, - perform: async (request, { settings, payload }) => { - return upsertRows(request, settings.subdomain, [payload]) + perform: async (request, { settings, payload, statsContext }) => { + return upsertRows(request, settings.subdomain, [payload], statsContext) }, - performBatch: async (request, { settings, payload }) => { - return upsertRows(request, settings.subdomain, payload) + performBatch: async (request, { settings, payload, statsContext }) => { + return upsertRows(request, settings.subdomain, payload, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/dataExtension/index.ts b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/dataExtension/index.ts index 37128dfbce..3519d654d1 100644 --- a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/dataExtension/index.ts +++ b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/dataExtension/index.ts @@ -15,11 +15,11 @@ const action: ActionDefinition = { enable_batching: enable_batching, batch_size: batch_size }, - perform: async (request, { settings, payload }) => { - return upsertRows(request, settings.subdomain, [payload]) + perform: async (request, { settings, payload, statsContext }) => { + return upsertRows(request, settings.subdomain, [payload], statsContext) }, - performBatch: async (request, { settings, payload }) => { - return upsertRows(request, settings.subdomain, payload) + performBatch: async (request, { settings, payload, statsContext }) => { + return upsertRows(request, settings.subdomain, payload, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/sfmc-operations.ts b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/sfmc-operations.ts index b4e2a312be..fbb898ffef 100644 --- a/packages/destination-actions/src/destinations/salesforce-marketing-cloud/sfmc-operations.ts +++ b/packages/destination-actions/src/destinations/salesforce-marketing-cloud/sfmc-operations.ts @@ -1,11 +1,12 @@ -import { RequestClient, IntegrationError } from '@segment/actions-core' +import { RequestClient, IntegrationError, StatsContext } from '@segment/actions-core' import { Payload as payload_dataExtension } from './dataExtension/generated-types' import { Payload as payload_contactDataExtension } from './contactDataExtension/generated-types' export function upsertRows( request: RequestClient, subdomain: String, - payloads: payload_dataExtension[] | payload_contactDataExtension[] + payloads: payload_dataExtension[] | payload_contactDataExtension[], + statsContext?: StatsContext ) { const { key, id } = payloads[0] if (!key && !id) { @@ -22,6 +23,7 @@ export function upsertRows( values: payload.values }) }) + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:data-extension`]) if (key) { return request(`https://${subdomain}.rest.marketingcloudapis.com/hub/v1/dataevents/key:${key}/rowset`, { method: 'POST', diff --git a/packages/destination-actions/src/destinations/salesforce/account/index.ts b/packages/destination-actions/src/destinations/salesforce/account/index.ts index bbb84e11b9..29f0fa9056 100644 --- a/packages/destination-actions/src/destinations/salesforce/account/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/account/index.ts @@ -180,34 +180,34 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { if (!payload.name) { throw new IntegrationError('Missing name value', 'Misconfigured required field', 400) } - return await sf.createRecord(payload, OBJECT_NAME) + return await sf.createRecord(payload, OBJECT_NAME, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, OBJECT_NAME) + return await sf.updateRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'upsert') { if (!payload.name) { throw new IntegrationError('Missing name value', 'Misconfigured required field', 400) } - return await sf.upsertRecord(payload, OBJECT_NAME) + return await sf.upsertRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, OBJECT_NAME) + return await sf.deleteRecord(payload, OBJECT_NAME, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload[0].operation === 'upsert') { @@ -216,7 +216,7 @@ const action: ActionDefinition = { } } - return sf.bulkHandler(payload, OBJECT_NAME) + return sf.bulkHandler(payload, OBJECT_NAME, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/cases/index.ts b/packages/destination-actions/src/destinations/salesforce/cases/index.ts index 599688a95d..704df6de69 100644 --- a/packages/destination-actions/src/destinations/salesforce/cases/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/cases/index.ts @@ -34,31 +34,31 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { - return await sf.createRecord(payload, OBJECT_NAME) + return await sf.createRecord(payload, OBJECT_NAME, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, OBJECT_NAME) + return await sf.updateRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'upsert') { - return await sf.upsertRecord(payload, OBJECT_NAME) + return await sf.upsertRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, OBJECT_NAME) + return await sf.deleteRecord(payload, OBJECT_NAME, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) - return sf.bulkHandler(payload, OBJECT_NAME) + return sf.bulkHandler(payload, OBJECT_NAME, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/contact/index.ts b/packages/destination-actions/src/destinations/salesforce/contact/index.ts index 95df669ca0..4b3c020ca5 100644 --- a/packages/destination-actions/src/destinations/salesforce/contact/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/contact/index.ts @@ -131,34 +131,34 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { if (!payload.last_name) { throw new IntegrationError('Missing last_name value', 'Misconfigured required field', 400) } - return await sf.createRecord(payload, OBJECT_NAME) + return await sf.createRecord(payload, OBJECT_NAME, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, OBJECT_NAME) + return await sf.updateRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'upsert') { if (!payload.last_name) { throw new IntegrationError('Missing last_name value', 'Misconfigured required field', 400) } - return await sf.upsertRecord(payload, OBJECT_NAME) + return await sf.upsertRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, OBJECT_NAME) + return await sf.deleteRecord(payload, OBJECT_NAME, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload[0].operation === 'upsert') { @@ -167,7 +167,7 @@ const action: ActionDefinition = { } } - return sf.bulkHandler(payload, OBJECT_NAME) + return sf.bulkHandler(payload, OBJECT_NAME, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/customObject/index.ts b/packages/destination-actions/src/destinations/salesforce/customObject/index.ts index 470ab23f10..3979cac042 100644 --- a/packages/destination-actions/src/destinations/salesforce/customObject/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/customObject/index.ts @@ -47,7 +47,7 @@ const action: ActionDefinition = { return sf.customObjectName() } }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { if (OPERATIONS_WITH_CUSTOM_FIELDS.includes(payload.operation) && !payload.customFields) { throw new PayloadValidationError('Custom fields are required for this operation.') } @@ -55,31 +55,31 @@ const action: ActionDefinition = { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { - return await sf.createRecord(payload, payload.customObjectName) + return await sf.createRecord(payload, payload.customObjectName, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, payload.customObjectName) + return await sf.updateRecord(payload, payload.customObjectName, statsContext) } if (payload.operation === 'upsert') { - return await sf.upsertRecord(payload, payload.customObjectName) + return await sf.upsertRecord(payload, payload.customObjectName, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, payload.customObjectName) + return await sf.deleteRecord(payload, payload.customObjectName, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { if (OPERATIONS_WITH_CUSTOM_FIELDS.includes(payload[0].operation) && !payload[0].customFields) { throw new PayloadValidationError('Custom fields are required for this operation.') } const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) - return sf.bulkHandler(payload, payload[0].customObjectName) + return sf.bulkHandler(payload, payload[0].customObjectName, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/lead/index.ts b/packages/destination-actions/src/destinations/salesforce/lead/index.ts index be5aff7008..0de088cd66 100644 --- a/packages/destination-actions/src/destinations/salesforce/lead/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/lead/index.ts @@ -138,34 +138,34 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { if (!payload.last_name) { throw new IntegrationError('Missing last_name value', 'Misconfigured required field', 400) } - return await sf.createRecord(payload, OBJECT_NAME) + return await sf.createRecord(payload, OBJECT_NAME, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, OBJECT_NAME) + return await sf.updateRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'upsert') { if (!payload.last_name) { throw new IntegrationError('Missing last_name value', 'Misconfigured required field', 400) } - return await sf.upsertRecord(payload, OBJECT_NAME) + return await sf.upsertRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, OBJECT_NAME) + return await sf.deleteRecord(payload, OBJECT_NAME, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload[0].operation === 'upsert') { @@ -174,7 +174,7 @@ const action: ActionDefinition = { } } - return sf.bulkHandler(payload, OBJECT_NAME) + return sf.bulkHandler(payload, OBJECT_NAME, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/opportunity/index.ts b/packages/destination-actions/src/destinations/salesforce/opportunity/index.ts index 9ec852e839..624a69e329 100644 --- a/packages/destination-actions/src/destinations/salesforce/opportunity/index.ts +++ b/packages/destination-actions/src/destinations/salesforce/opportunity/index.ts @@ -55,34 +55,34 @@ const action: ActionDefinition = { }, customFields: customFields }, - perform: async (request, { settings, payload }) => { + perform: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload.operation === 'create') { if (!payload.close_date || !payload.name || !payload.stage_name) { throw new IntegrationError('Missing close_date, name or stage_name value', 'Misconfigured required field', 400) } - return await sf.createRecord(payload, OBJECT_NAME) + return await sf.createRecord(payload, OBJECT_NAME, statsContext) } validateLookup(payload) if (payload.operation === 'update') { - return await sf.updateRecord(payload, OBJECT_NAME) + return await sf.updateRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'upsert') { if (!payload.close_date || !payload.name || !payload.stage_name) { throw new IntegrationError('Missing close_date, name or stage_name value', 'Misconfigured required field', 400) } - return await sf.upsertRecord(payload, OBJECT_NAME) + return await sf.upsertRecord(payload, OBJECT_NAME, statsContext) } if (payload.operation === 'delete') { - return await sf.deleteRecord(payload, OBJECT_NAME) + return await sf.deleteRecord(payload, OBJECT_NAME, statsContext) } }, - performBatch: async (request, { settings, payload }) => { + performBatch: async (request, { settings, payload, statsContext }) => { const sf: Salesforce = new Salesforce(settings.instanceUrl, await generateSalesforceRequest(settings, request)) if (payload[0].operation === 'upsert') { @@ -91,7 +91,7 @@ const action: ActionDefinition = { } } - return sf.bulkHandler(payload, OBJECT_NAME) + return sf.bulkHandler(payload, OBJECT_NAME, statsContext) } } diff --git a/packages/destination-actions/src/destinations/salesforce/sf-operations.ts b/packages/destination-actions/src/destinations/salesforce/sf-operations.ts index 5076107d64..ad6c4f52c8 100644 --- a/packages/destination-actions/src/destinations/salesforce/sf-operations.ts +++ b/packages/destination-actions/src/destinations/salesforce/sf-operations.ts @@ -1,4 +1,10 @@ -import { IntegrationError, ModifiedResponse, RequestClient, RefreshAccessTokenResult } from '@segment/actions-core' +import { + IntegrationError, + ModifiedResponse, + RequestClient, + RefreshAccessTokenResult, + StatsContext +} from '@segment/actions-core' import type { GenericPayload } from './sf-types' import { mapObjectToShape } from './sf-object-to-shape' import { buildCSVData, validateInstanceURL } from './sf-utils' @@ -154,8 +160,12 @@ export default class Salesforce { this.request = request } - createRecord = async (payload: GenericPayload, sobject: string) => { + createRecord = async (payload: GenericPayload, sobject: string, statsContext?: StatsContext) => { const json = this.buildJSONData(payload, sobject) + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:create-salesforce-object` + ]) return this.request(`${this.instanceUrl}services/data/${API_VERSION}/sobjects/${sobject}`, { method: 'post', @@ -163,13 +173,13 @@ export default class Salesforce { }) } - updateRecord = async (payload: GenericPayload, sobject: string) => { + updateRecord = async (payload: GenericPayload, sobject: string, statsContext?: StatsContext) => { if (!payload.traits || Object.keys(payload.traits).length === 0) { throw new IntegrationError('Undefined Traits when using update operation', 'Undefined Traits', 400) } if (Object.keys(payload.traits).includes('Id') && payload.traits['Id']) { - return await this.baseUpdate(payload.traits['Id'] as string, sobject, payload) + return await this.baseUpdate(payload.traits['Id'] as string, sobject, payload, statsContext) } const soqlOperator: SOQLOperator = validateSOQLOperator(payload.recordMatcherOperator) @@ -179,10 +189,10 @@ export default class Salesforce { throw err } - return await this.baseUpdate(recordId, sobject, payload) + return await this.baseUpdate(recordId, sobject, payload, statsContext) } - upsertRecord = async (payload: GenericPayload, sobject: string) => { + upsertRecord = async (payload: GenericPayload, sobject: string, statsContext?: StatsContext) => { if (!payload.traits || Object.keys(payload.traits).length === 0) { throw new IntegrationError('Undefined Traits when using upsert operation', 'Undefined Traits', 400) } @@ -192,20 +202,20 @@ export default class Salesforce { if (err) { if (err.status === 404) { - return await this.createRecord(payload, sobject) + return await this.createRecord(payload, sobject, statsContext) } throw err } - return await this.baseUpdate(recordId, sobject, payload) + return await this.baseUpdate(recordId, sobject, payload, statsContext) } - deleteRecord = async (payload: GenericPayload, sobject: string) => { + deleteRecord = async (payload: GenericPayload, sobject: string, statsContext?: StatsContext) => { if (!payload.traits || Object.keys(payload.traits).length === 0) { throw new IntegrationError('Undefined Traits when using delete operation', 'Undefined Traits', 400) } if (Object.keys(payload.traits).includes('Id') && payload.traits['Id']) { - return await this.baseDelete(payload.traits['Id'] as string, sobject) + return await this.baseDelete(payload.traits['Id'] as string, sobject, statsContext) } const soqlOperator: SOQLOperator = validateSOQLOperator(payload.recordMatcherOperator) @@ -215,20 +225,20 @@ export default class Salesforce { throw err } - return await this.baseDelete(recordId, sobject) + return await this.baseDelete(recordId, sobject, statsContext) } - bulkHandler = async (payloads: GenericPayload[], sobject: string) => { + bulkHandler = async (payloads: GenericPayload[], sobject: string, statsContext?: StatsContext) => { if (!payloads[0].enable_batching) { throwBulkMismatchError() } if (payloads[0].operation === 'upsert') { - return await this.bulkUpsert(payloads, sobject) + return await this.bulkUpsert(payloads, sobject, statsContext) } else if (payloads[0].operation === 'update') { - return await this.bulkUpdate(payloads, sobject) + return await this.bulkUpdate(payloads, sobject, statsContext) } else if (payloads[0].operation === 'create') { - return await this.bulkInsert(payloads, sobject) + return await this.bulkInsert(payloads, sobject, statsContext) } if (payloads[0].operation === 'delete') { @@ -274,12 +284,12 @@ export default class Salesforce { } } - private bulkInsert = async (payloads: GenericPayload[], sobject: string) => { + private bulkInsert = async (payloads: GenericPayload[], sobject: string, statsContext?: StatsContext) => { // The idField is purposely passed as an empty string since the field is not required. - return this.handleBulkJob(payloads, sobject, '', 'insert') + return this.handleBulkJob(payloads, sobject, '', 'insert', statsContext) } - private bulkUpsert = async (payloads: GenericPayload[], sobject: string) => { + private bulkUpsert = async (payloads: GenericPayload[], sobject: string, statsContext?: StatsContext) => { if ( !payloads[0].bulkUpsertExternalId || !payloads[0].bulkUpsertExternalId.externalIdName || @@ -292,10 +302,10 @@ export default class Salesforce { ) } const externalIdFieldName = payloads[0].bulkUpsertExternalId.externalIdName - return this.handleBulkJob(payloads, sobject, externalIdFieldName, 'upsert') + return this.handleBulkJob(payloads, sobject, externalIdFieldName, 'upsert', statsContext) } - private bulkUpdate = async (payloads: GenericPayload[], sobject: string) => { + private bulkUpdate = async (payloads: GenericPayload[], sobject: string, statsContext?: StatsContext) => { if (!payloads[0].bulkUpdateRecordId) { throw new IntegrationError( 'Undefined bulkUpdateRecordId when using bulkUpdate operation', @@ -304,35 +314,46 @@ export default class Salesforce { ) } - return this.handleBulkJob(payloads, sobject, 'Id', 'update') + return this.handleBulkJob(payloads, sobject, 'Id', 'update', statsContext) } private async handleBulkJob( payloads: GenericPayload[], sobject: string, idField: string, - operation: string + operation: string, + statsContext?: StatsContext ): Promise> { // construct the CSV data to catch errors before creating a bulk job const csv = buildCSVData(payloads, idField) - const jobId = await this.createBulkJob(sobject, idField, operation) + const jobId = await this.createBulkJob(sobject, idField, operation, statsContext) try { - await this.uploadBulkCSV(jobId, csv) + await this.uploadBulkCSV(jobId, csv, statsContext) } catch (err) { // always close the "bulk job" otherwise it will get // stuck in "pending". // // run in background to ensure this service has time to respond // with useful information before the connection closes. - this.closeBulkJob(jobId).catch((_) => { + this.closeBulkJob(jobId, statsContext).catch((_) => { // ignore close error to avoid masking the root error }) throw err } - return await this.closeBulkJob(jobId) + return await this.closeBulkJob(jobId, statsContext) } - private createBulkJob = async (sobject: string, externalIdFieldName: string, operation: string) => { + private createBulkJob = async ( + sobject: string, + externalIdFieldName: string, + operation: string, + statsContext?: StatsContext + ) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `operation:${operation}`, + `endpoint:create-bulk-job` + ]) const jsonData: { object: string; contentType: 'CSV'; operation: string; externalIdFieldName?: string } = { object: sobject, contentType: 'CSV', @@ -342,7 +363,7 @@ export default class Salesforce { if (operation === 'update' || operation === 'upsert') { jsonData.externalIdFieldName = externalIdFieldName } - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:create-bulk-job`]) const res = await this.request( `${this.instanceUrl}services/data/${API_VERSION}/jobs/ingest`, { @@ -358,7 +379,11 @@ export default class Salesforce { return res.data.id } - private uploadBulkCSV = async (jobId: string, csv: string) => { + private uploadBulkCSV = async (jobId: string, csv: string, statsContext?: StatsContext) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:upload-batched-job-data-in-csv` + ]) return this.request(`${this.instanceUrl}services/data/${API_VERSION}/jobs/ingest/${jobId}/batches`, { method: 'put', headers: { @@ -369,7 +394,8 @@ export default class Salesforce { }) } - private closeBulkJob = async (jobId: string) => { + private closeBulkJob = async (jobId: string, statsContext?: StatsContext) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [...statsContext?.tags, `endpoint:close-job`]) return this.request(`${this.instanceUrl}services/data/${API_VERSION}/jobs/ingest/${jobId}`, { method: 'PATCH', json: { @@ -378,16 +404,28 @@ export default class Salesforce { }) } - private baseUpdate = async (recordId: string, sobject: string, payload: GenericPayload) => { + private baseUpdate = async ( + recordId: string, + sobject: string, + payload: GenericPayload, + statsContext?: StatsContext + ) => { const json = this.buildJSONData(payload, sobject) - + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:update-salesforce-object` + ]) return this.request(`${this.instanceUrl}services/data/${API_VERSION}/sobjects/${sobject}/${recordId}`, { method: 'patch', json: json }) } - private baseDelete = async (recordId: string, sobject: string) => { + private baseDelete = async (recordId: string, sobject: string, statsContext?: StatsContext) => { + statsContext?.statsClient?.incr('oauth_app_api_call', 1, [ + ...statsContext?.tags, + `endpoint:delete-salesforce-object` + ]) return this.request(`${this.instanceUrl}services/data/${API_VERSION}/sobjects/${sobject}/${recordId}`, { method: 'delete' })