From 8afca639a85e0c06ba908d985aed796e03543aa6 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Tue, 13 Aug 2024 14:42:40 -0400 Subject: [PATCH 01/11] add dv360 create and get audience methods --- .../first-party-dv360/functions.ts | 58 +++++++ .../first-party-dv360/generated-types.ts | 24 +++ .../destinations/first-party-dv360/index.ts | 142 ++++++++++++++++-- 3 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/functions.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts new file mode 100644 index 0000000000..b63eddbb17 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -0,0 +1,58 @@ +import { RequestClient } from '@segment/actions-core' + +const DV360API = `https://displayvideo.googleapis.com/v3/firstAndThirdPartyAudiences` + +interface createAudienceRequestParams { + advertiserId: string + audienceName: string + description?: string + membershipDurationDays: string + audienceType: string + token?: string +} + +interface getAudienceParams { + advertiserId: string + audienceId: string + token?: string +} + +export const createAudienceRequest = ( + _request: RequestClient, + params: createAudienceRequestParams +): Promise => { + const { advertiserId, audienceName, description, membershipDurationDays, audienceType, token } = params + + const endpoint = DV360API + `?advertiserId=${advertiserId}` + + return _request(endpoint, { + method: 'POST', + headers: { + authorization: `Bearer ${token}`, + 'Content-Type': 'application/json; charset=utf-8' + }, + json: { + displayName: audienceName, + audienceType: audienceType, + membershipDurationDays: membershipDurationDays, + description: description, + audienceSource: 'AUDIENCE_SOURCE_UNSPECIFIED', + firstAndThirdPartyAudienceType: 'FIRST_AND_THIRD_PARTY_AUDIENCE_TYPE_FIRST_PARTY' + } + }) +} + +export const getAudienceRequest = (_request: RequestClient, params: getAudienceParams): Promise => { + const { advertiserId, audienceId, token } = params + + const endpoint = DV360API + `/${audienceId}?advertiserId=${advertiserId}` + console.log(endpoint) + + return _request(endpoint, { + method: 'GET', + headers: { + authorization: `Bearer ${token}`, + 'Content-Type': 'application/json; charset=utf-8' + } + }) +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/generated-types.ts index 4ab2786ec6..0e0e859cc7 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/generated-types.ts @@ -1,3 +1,27 @@ // Generated file. DO NOT MODIFY IT BY HAND. export interface Settings {} +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface AudienceSettings { + /** + * The ID of your advertiser, used throughout Display & Video 360. Use this ID when you contact Display & Video 360 support to help our teams locate your specific account. + */ + advertiserId: string + /** + * The type of the audience. + */ + audienceType: string + /** + * The description of the audience. + */ + description?: string + /** + * The duration in days that an entry remains in the audience after the qualifying event. If the audience has no expiration, set the value of this field to 10000. Otherwise, the set value must be greater than 0 and less than or equal to 540. + */ + membershipDurationDays: string + /** + * temp until we build authentication flow + */ + token: string +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/index.ts index 12ab1d4ee3..86d85c7ce0 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/index.ts @@ -1,12 +1,51 @@ -import type { AudienceDestinationDefinition } from '@segment/actions-core' -import type { Settings } from './generated-types' +import { AudienceDestinationDefinition, IntegrationError } from '@segment/actions-core' +import type { AudienceSettings, Settings } from './generated-types' import addToList from './addToList' +import { createAudienceRequest, getAudienceRequest } from './functions' -const destination: AudienceDestinationDefinition = { +const destination: AudienceDestinationDefinition = { name: 'First Party Dv360', slug: 'actions-first-party-dv360', mode: 'cloud', + audienceFields: { + advertiserId: { + type: 'string', + label: 'Advertiser ID', + required: true, + description: + 'The ID of your advertiser, used throughout Display & Video 360. Use this ID when you contact Display & Video 360 support to help our teams locate your specific account.' + }, + audienceType: { + type: 'string', + label: 'Audience Type', + choices: [ + { label: 'CUSTOMER MATCH CONTACT INFO', value: 'CUSTOMER_MATCH_CONTACT_INFO' }, + { label: 'CUSTOMER MATCH DEVICE ID', value: 'CUSTOMER_MATCH_DEVICE_ID' } + ], + required: true, + description: 'The type of the audience.' + }, + description: { + type: 'string', + label: 'Description', + required: false, + description: 'The description of the audience.' + }, + membershipDurationDays: { + type: 'string', + label: 'Membership Duration Days', + required: true, + description: + 'The duration in days that an entry remains in the audience after the qualifying event. If the audience has no expiration, set the value of this field to 10000. Otherwise, the set value must be greater than 0 and less than or equal to 540.' + }, + token: { + type: 'string', + label: 'Auth Token', + required: true, + description: 'temp until we build authentication flow' + } + }, authentication: { scheme: 'custom', @@ -16,20 +55,105 @@ const destination: AudienceDestinationDefinition = { } }, - audienceFields: {}, - audienceConfig: { mode: { type: 'synced', full_audience_sync: false }, - createAudience: async (_request, _createAudienceInput) => { - return { externalId: '' } + createAudience: async (_request, createAudienceInput) => { + // Extract values from input + const { audienceName, audienceSettings, statsContext } = createAudienceInput + const advertiserId = audienceSettings?.advertiserId?.trim() + const description = audienceSettings?.description + const membershipDurationDays = audienceSettings?.membershipDurationDays + const audienceType = audienceSettings?.audienceType + const token = audienceSettings?.token // Temporary token variable + + // Update statistics tags and sends a call metric to Datadog. Ensures that datadog is infomred 'createAudience' operation was invoked + const statsName = 'createAudience' + const { statsClient, tags: statsTags } = statsContext || {} + statsTags?.push(`slug:${destination.slug}`) + statsClient?.incr(`${statsName}.call`, 1, statsTags) + + // Validate required fields and throws errors if any are missing. + if (!audienceName) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Missing audience name value', 'MISSING_REQUIRED_FIELD', 400) + } + + if (!advertiserId) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Missing advertiser ID value', 'MISSING_REQUIRED_FIELD', 400) + } + + if (!membershipDurationDays) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Missing membership duration days value', 'MISSING_REQUIRED_FIELD', 400) + } + + if (!audienceType) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Missing audience type value', 'MISSING_REQUIRED_FIELD', 400) + } + + // Make API request to create the audience + const response = await createAudienceRequest(_request, { + advertiserId, + audienceName, + description, + membershipDurationDays, + audienceType, + token + }) + + // Parse and return the externalId + const r = await response.json() + statsClient?.incr(`${statsName}.success`, 1, statsTags) + return { + externalId: r.firstAndThirdPartyAudienceId + } }, - getAudience: async (_request, _getAudienceInput) => { - return { externalId: '' } + getAudience: async (_request, getAudienceInput) => { + // Extract values from input + const { audienceSettings, statsContext } = getAudienceInput + const audienceId = getAudienceInput.externalId + const advertiserId = audienceSettings?.advertiserId?.trim() + const token = audienceSettings?.token // Temporary token variable + + // Update statistics tags and sends a call metric to Datadog. Ensures that datadog is infomred 'getAudience' operation was invoked + const statsName = 'getAudience' + const { statsClient, tags: statsTags } = statsContext || {} + statsTags?.push(`slug:${destination.slug}`) + statsClient?.incr(`${statsName}.call`, 1, statsTags) + + if (!advertiserId) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Missing required advertiser ID value', 'MISSING_REQUIRED_FIELD', 400) + } + + // Make API request to get audience details + const response = await getAudienceRequest(_request, { advertiserId, audienceId, token }) + + if (!response.ok) { + // Handle non-OK responses + statsTags?.push('error:api-request-failed') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Failed to retrieve audience details', 'API_REQUEST_FAILED', response.status) + } + + // Parse and return the response + const audienceData = await response.json() + statsClient?.incr(`${statsName}.success`, 1, statsTags) + return { + externalId: audienceData.firstAndThirdPartyAudienceId + } } }, From a00c231cc1476a59553a94003d1f57b81cfc5f2b Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Wed, 4 Sep 2024 14:59:26 -0400 Subject: [PATCH 02/11] update code from pr comments --- .../src/destinations/first-party-dv360/functions.ts | 1 - .../src/destinations/first-party-dv360/index.ts | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index b63eddbb17..3aedb04e2a 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -46,7 +46,6 @@ export const getAudienceRequest = (_request: RequestClient, params: getAudienceP const { advertiserId, audienceId, token } = params const endpoint = DV360API + `/${audienceId}?advertiserId=${advertiserId}` - console.log(endpoint) return _request(endpoint, { method: 'GET', diff --git a/packages/destination-actions/src/destinations/first-party-dv360/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/index.ts index 86d85c7ce0..7e3ba27971 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/index.ts @@ -138,6 +138,12 @@ const destination: AudienceDestinationDefinition = { throw new IntegrationError('Missing required advertiser ID value', 'MISSING_REQUIRED_FIELD', 400) } + if (!audienceId) { + statsTags?.push('error:missing-settings') + statsClient?.incr(`${statsName}.error`, 1, statsTags) + throw new IntegrationError('Failed to retrieve audience ID', 'MISSING_REQUIRED_FIELD', 400) + } + // Make API request to get audience details const response = await getAudienceRequest(_request, { advertiserId, audienceId, token }) From 6d395c9bedd7ecb6817ec4bb8a62b4e5d1a922f4 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Wed, 4 Sep 2024 15:08:36 -0400 Subject: [PATCH 03/11] remove _ and console.log --- .../src/destinations/first-party-dv360/functions.ts | 8 ++++---- .../src/destinations/first-party-dv360/index.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index 3aedb04e2a..fba5d7f9cb 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -18,14 +18,14 @@ interface getAudienceParams { } export const createAudienceRequest = ( - _request: RequestClient, + request: RequestClient, params: createAudienceRequestParams ): Promise => { const { advertiserId, audienceName, description, membershipDurationDays, audienceType, token } = params const endpoint = DV360API + `?advertiserId=${advertiserId}` - return _request(endpoint, { + return request(endpoint, { method: 'POST', headers: { authorization: `Bearer ${token}`, @@ -42,12 +42,12 @@ export const createAudienceRequest = ( }) } -export const getAudienceRequest = (_request: RequestClient, params: getAudienceParams): Promise => { +export const getAudienceRequest = (request: RequestClient, params: getAudienceParams): Promise => { const { advertiserId, audienceId, token } = params const endpoint = DV360API + `/${audienceId}?advertiserId=${advertiserId}` - return _request(endpoint, { + return request(endpoint, { method: 'GET', headers: { authorization: `Bearer ${token}`, diff --git a/packages/destination-actions/src/destinations/first-party-dv360/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/index.ts index 7e3ba27971..df831dcf02 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/index.ts @@ -141,7 +141,7 @@ const destination: AudienceDestinationDefinition = { if (!audienceId) { statsTags?.push('error:missing-settings') statsClient?.incr(`${statsName}.error`, 1, statsTags) - throw new IntegrationError('Failed to retrieve audience ID', 'MISSING_REQUIRED_FIELD', 400) + throw new IntegrationError('Failed to retrieve audience ID value', 'MISSING_REQUIRED_FIELD', 400) } // Make API request to get audience details From 05bef4a13b5030444cd51e9e0417edabad2a8d1b Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Mon, 9 Sep 2024 14:15:03 -0400 Subject: [PATCH 04/11] debugging with Marin --- .vscode/launch.json | 2 +- packages/cli/src/lib/server.ts | 3 + .../addToList/generated-types.ts | 28 +++- .../first-party-dv360/addToList/index.ts | 34 +++-- .../first-party-dv360/functions.ts | 127 +++++++++++++++++- 5 files changed, 180 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 61877fb64f..836e755d92 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "name": "Start dev server", "cwd": "${workspaceFolder}", "runtimeExecutable": "yarn", - "runtimeArgs": ["server", "dev"], + "runtimeArgs": ["serve", "dev"], "restart": true, "console": "integratedTerminal", "port": 47082, diff --git a/packages/cli/src/lib/server.ts b/packages/cli/src/lib/server.ts index 8a68fa4aa9..fb7f7e0a0b 100644 --- a/packages/cli/src/lib/server.ts +++ b/packages/cli/src/lib/server.ts @@ -302,8 +302,11 @@ function setupRoutes(def: DestinationDefinition | null): void { if (Array.isArray(eventParams.data)) { // If no mapping or default mapping is provided, default to using the first payload across all events. + console.log('ContextParams:', req.body.payload[0].context?.personas?.external_audience_id) eventParams.mapping = mapping || eventParams.data[0] || {} eventParams.audienceSettings = req.body.payload[0]?.context?.personas?.audience_settings || {} + console.log('EventParams2', eventParams) + console.log('EventParamContext:', eventParams.data[0].context) await action.executeBatch(eventParams) } else { await action.execute(eventParams) diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts index 944d22b085..b830d6e603 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts @@ -1,3 +1,29 @@ // Generated file. DO NOT MODIFY IT BY HAND. -export interface Payload {} +export interface Payload { + external_id: any + /** + * A list of the user's emails. If not already hashed, the system will hash them before use. + */ + emails?: string + /** + * A list of the user's phone numbers. If not already hashed, the system will hash them before use. + */ + phoneNumbers?: string + /** + * A list of the user's zip codes. + */ + zipCodes?: string + /** + * The user's first name. If not already hashed, the system will hash it before use. + */ + firstName?: string + /** + * The user's last name. If not already hashed, the system will hash it before use. + */ + lastName?: string + /** + * The country code of the user. + */ + countryCode?: string +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts index 811316a20c..1cd060c3ff 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts @@ -1,17 +1,29 @@ import type { ActionDefinition } from '@segment/actions-core' -import type { Settings } from '../generated-types' +import type { AudienceSettings } from '../generated-types' import type { Payload } from './generated-types' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode } from '../properties' +import { addCustomerMatchMembers } from '../functions' -const action: ActionDefinition = { - title: 'Add to List', - description: 'Adds to list', - fields: {}, - perform: (_request, _data) => { - // Make your partner api request here! - // return request('https://example.com', { - // method: 'post', - // json: data.payload - // }) +const action: ActionDefinition = { + title: 'Edit Customer Match Members', + description: 'Add or update customer match members in Google Display & Video 360.', + defaultSubscription: 'event = "Audeince Entered"', + fields: { + emails: { ...emails }, + phoneNumbers: { ...phoneNumbers }, + zipCodes: { ...zipCodes }, + firstName: { ...firstName }, + lastName: { ...lastName }, + countryCode: { ...countryCode } + }, + perform: async (request, { settings, payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return addCustomerMatchMembers(request, settings, [payload]) + }, + performBatch: async (request, { settings, payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + console.log('Pre-Payload', payload) + return addCustomerMatchMembers(request, settings, payload) } } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index fba5d7f9cb..0cb846e502 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -1,6 +1,11 @@ -import { RequestClient } from '@segment/actions-core' +import { IntegrationError, RequestClient, StatsContext } from '@segment/actions-core' +import { AudienceSettings } from './generated-types' +import { Payload } from './addToList/generated-types' +import { createHash } from 'crypto' const DV360API = `https://displayvideo.googleapis.com/v3/firstAndThirdPartyAudiences` +const MAX_REQUEST_SIZE = 500000 +const CONSENT_STATUS_GRANTED = 'CONSENT_STATUS_GRANTED' // Define consent status interface createAudienceRequestParams { advertiserId: string @@ -17,6 +22,17 @@ interface getAudienceParams { token?: string } +interface DV360editCustomerMatchResponse { + firstAndThirdPartyAudienceId: string + error: [ + { + code: string + message: string + status: string + } + ] +} + export const createAudienceRequest = ( request: RequestClient, params: createAudienceRequestParams @@ -55,3 +71,112 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa } }) } + +export async function addCustomerMatchMembers( + request: RequestClient, + settings: AudienceSettings, + payloads: Payload[], + statsContext?: StatsContext // Adjust type based on actual stats context +) { + console.log('Payload:', payloads) + // Use the audience ID from the first payload + const audienceId = payloads[0].external_id + const endpoint = DV360API + '${audienceId}:editCustomerMatchMembers' + + if (!audienceId) { + throw new IntegrationError('No external ID found in payload', 'INVALID_REQUEST_DATA', 400) + } + + // Assuming we're dealing with a single payload + const payload = payloads[0] + + // Prepare the request payload + const contactInfoList = { + contactInfos: [processPayload(payload)], + consent: { + adUserData: CONSENT_STATUS_GRANTED, + adPersonalization: CONSENT_STATUS_GRANTED + } + } + + // Convert the payload to string if needed + const requestPayload = JSON.stringify({ + advertiserId: settings.advertiserId, + addedContactInfoList: contactInfoList + }) + + // Ensure the request data size is within acceptable limits + console.log('FIX') + const requestSize = Buffer.byteLength(requestPayload, 'utf8') + if (requestSize > MAX_REQUEST_SIZE) { + statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) + throw new IntegrationError( + `Request data size exceeds limit of ${MAX_REQUEST_SIZE} bytes`, + 'INVALID_REQUEST_DATA', + 400 + ) + } + + console.log('Payload Formatted:', requestPayload) + + const response = await request( + endpoint, + { + method: 'POST', + headers: { + authorization: `Bearer ${settings.token}`, + 'Content-Type': 'application/json; charset=utf-8' + }, + body: requestPayload + } + // Handle the API response generically + ) + + // Handle the API response generically + if (!response.data || !response.data.firstAndThirdPartyAudienceId) { + statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) + throw new IntegrationError( + `API returned error: ${response.data?.error || 'Unknown error'}`, + 'API_REQUEST_ERROR', + 400 + ) + } + + statsContext?.statsClient?.incr('addCustomerMatchMembers.success', 1, statsContext?.tags) + return response.data +} + +function normalizeAndHash(data: string) { + // Normalize the data + const normalizedData = data.toLowerCase().trim() // Example: Convert to lowercase and remove leading/trailing spaces + // Hash the normalized data using SHA-256 + const hash = createHash('sha256') + hash.update(normalizedData) + return hash.digest('hex') +} + +function processPayload(payload: Payload) { + const result: { [key: string]: string } = {} + + // Normalize and hash only if the value is defined + if (payload.emails) { + result.hashedEmail = normalizeAndHash(payload.emails) + } + if (payload.phoneNumbers) { + result.hashedPhoneNumber = normalizeAndHash(payload.phoneNumbers) + } + if (payload.zipCodes) { + result.zipCode = normalizeAndHash(payload.zipCodes) + } + if (payload.firstName) { + result.firstName = normalizeAndHash(payload.firstName) + } + if (payload.lastName) { + result.lastName = normalizeAndHash(payload.lastName) + } + if (payload.countryCode) { + result.countryCode = normalizeAndHash(payload.countryCode) + } + + return result +} From 827feb591930554d4394a41098f8553875c04161 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Mon, 9 Sep 2024 14:22:17 -0400 Subject: [PATCH 05/11] add properites file --- .../first-party-dv360/properties.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/properties.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/properties.ts b/packages/destination-actions/src/destinations/first-party-dv360/properties.ts new file mode 100644 index 0000000000..a54f768f84 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/properties.ts @@ -0,0 +1,74 @@ +import { InputField } from '@segment/actions-core/index' + +export const external_id: InputField = { + label: 'External ID', + description: 'The ID of the DV360 Audience.', + type: 'string', + default: { + '@path': '$.context.personas.external_audience_id' + }, + unsafe_hidden: true +} + +export const emails: InputField = { + label: 'Emails', + description: `A list of the user's emails. If not already hashed, the system will hash them before use.`, + type: 'string', + default: { + '@path': '$.properties.emails' + } +} + +export const phoneNumbers: InputField = { + label: 'Phone Numbers', + description: `A list of the user's phone numbers. If not already hashed, the system will hash them before use.`, + type: 'string', + default: { + '@path': '$.properties.phoneNumbers' + } +} + +export const zipCodes: InputField = { + label: 'ZIP Codes', + description: `A list of the user's zip codes.`, + type: 'string', + default: { + '@path': '$.properties.zipCodes' + } +} + +export const firstName: InputField = { + label: 'First Name', + description: `The user's first name. If not already hashed, the system will hash it before use.`, + type: 'string', + default: { + '@path': '$.properties.firstName' + } +} + +export const lastName: InputField = { + label: 'Last Name', + description: `The user's last name. If not already hashed, the system will hash it before use.`, + type: 'string', + default: { + '@path': '$.properties.lastName' + } +} + +export const countryCode: InputField = { + label: 'Country Code', + description: `The country code of the user.`, + type: 'string', + default: { + '@path': '$.properties.countryCode' + } +} + +export const mobileDeviceIds: InputField = { + label: 'Mobile Device IDs', + description: `A list of mobile device IDs defining Customer Match audience members. The size of mobileDeviceIds mustn't be greater than 500,000.`, + type: 'string', + default: { + '@path': '$.properties.mobileDeviceIds' + } +} From 8311638f9b1275bb1b09e7999680894462b92eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADn=20Alcaraz?= Date: Mon, 9 Sep 2024 11:55:34 -0700 Subject: [PATCH 06/11] Work in progress --- packages/cli/src/lib/server.ts | 3 --- .../addToList/generated-types.ts | 5 ++++- .../first-party-dv360/addToList/index.ts | 17 +++++++++-------- .../destinations/first-party-dv360/functions.ts | 3 +-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/lib/server.ts b/packages/cli/src/lib/server.ts index fb7f7e0a0b..8a68fa4aa9 100644 --- a/packages/cli/src/lib/server.ts +++ b/packages/cli/src/lib/server.ts @@ -302,11 +302,8 @@ function setupRoutes(def: DestinationDefinition | null): void { if (Array.isArray(eventParams.data)) { // If no mapping or default mapping is provided, default to using the first payload across all events. - console.log('ContextParams:', req.body.payload[0].context?.personas?.external_audience_id) eventParams.mapping = mapping || eventParams.data[0] || {} eventParams.audienceSettings = req.body.payload[0]?.context?.personas?.audience_settings || {} - console.log('EventParams2', eventParams) - console.log('EventParamContext:', eventParams.data[0].context) await action.executeBatch(eventParams) } else { await action.execute(eventParams) diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts index b830d6e603..7f33875bab 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts @@ -1,7 +1,6 @@ // Generated file. DO NOT MODIFY IT BY HAND. export interface Payload { - external_id: any /** * A list of the user's emails. If not already hashed, the system will hash them before use. */ @@ -26,4 +25,8 @@ export interface Payload { * The country code of the user. */ countryCode?: string + /** + * The ID of the DV360 Audience. + */ + external_id?: string } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts index 1cd060c3ff..818ff9b788 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts @@ -1,10 +1,10 @@ import type { ActionDefinition } from '@segment/actions-core' -import type { AudienceSettings } from '../generated-types' +import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' -import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode } from '../properties' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' import { addCustomerMatchMembers } from '../functions' -const action: ActionDefinition = { +const action: ActionDefinition = { title: 'Edit Customer Match Members', description: 'Add or update customer match members in Google Display & Video 360.', defaultSubscription: 'event = "Audeince Entered"', @@ -14,16 +14,17 @@ const action: ActionDefinition = { zipCodes: { ...zipCodes }, firstName: { ...firstName }, lastName: { ...lastName }, - countryCode: { ...countryCode } + countryCode: { ...countryCode }, + external_id }, - perform: async (request, { settings, payload, statsContext }) => { + perform: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - return addCustomerMatchMembers(request, settings, [payload]) + return addCustomerMatchMembers(request, [payload], statsContext) }, - performBatch: async (request, { settings, payload, statsContext }) => { + performBatch: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) console.log('Pre-Payload', payload) - return addCustomerMatchMembers(request, settings, payload) + return addCustomerMatchMembers(request, payload, statsContext) } } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index 0cb846e502..e79a74427f 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -74,12 +74,11 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa export async function addCustomerMatchMembers( request: RequestClient, - settings: AudienceSettings, payloads: Payload[], statsContext?: StatsContext // Adjust type based on actual stats context ) { console.log('Payload:', payloads) - // Use the audience ID from the first payload + const settings = {} as AudienceSettings const audienceId = payloads[0].external_id const endpoint = DV360API + '${audienceId}:editCustomerMatchMembers' From 1f582360a5555dfffca34885fbf07f98d9093788 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Thu, 12 Sep 2024 13:40:49 -0700 Subject: [PATCH 07/11] add to audience actions --- .../generated-types.ts | 0 .../addToAudMobileDeviceId/generated-types.ts | 12 +++ .../first-party-dv360/addToList/index.ts | 31 ------ .../first-party-dv360/functions.ts | 100 +++++++++++++----- .../destinations/first-party-dv360/index.ts | 7 +- 5 files changed, 91 insertions(+), 59 deletions(-) rename packages/destination-actions/src/destinations/first-party-dv360/{addToList => addToAudContactInfo}/generated-types.ts (100%) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts delete mode 100644 packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts similarity index 100% rename from packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts rename to packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts new file mode 100644 index 0000000000..bc1f95ce51 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts @@ -0,0 +1,12 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * A list of mobile device IDs defining Customer Match audience members. The size of mobileDeviceIds mustn't be greater than 500,000. + */ + mobileDeviceIds?: string + /** + * The ID of the DV360 Audience. + */ + external_id?: string +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts deleted file mode 100644 index 818ff9b788..0000000000 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ActionDefinition } from '@segment/actions-core' -import type { AudienceSettings, Settings } from '../generated-types' -import type { Payload } from './generated-types' -import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' -import { addCustomerMatchMembers } from '../functions' - -const action: ActionDefinition = { - title: 'Edit Customer Match Members', - description: 'Add or update customer match members in Google Display & Video 360.', - defaultSubscription: 'event = "Audeince Entered"', - fields: { - emails: { ...emails }, - phoneNumbers: { ...phoneNumbers }, - zipCodes: { ...zipCodes }, - firstName: { ...firstName }, - lastName: { ...lastName }, - countryCode: { ...countryCode }, - external_id - }, - perform: async (request, { payload, statsContext }) => { - statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - return addCustomerMatchMembers(request, [payload], statsContext) - }, - performBatch: async (request, { payload, statsContext }) => { - statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) - console.log('Pre-Payload', payload) - return addCustomerMatchMembers(request, payload, statsContext) - } -} - -export default action diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index e79a74427f..8ff0cc0055 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -1,7 +1,8 @@ import { IntegrationError, RequestClient, StatsContext } from '@segment/actions-core' import { AudienceSettings } from './generated-types' -import { Payload } from './addToList/generated-types' +import { Payload } from './addToAudContactInfo/generated-types' import { createHash } from 'crypto' +import { Payload as DeviceIdPayload } from './addToAudMobileDeviceId/generated-types' const DV360API = `https://displayvideo.googleapis.com/v3/firstAndThirdPartyAudiences` const MAX_REQUEST_SIZE = 500000 @@ -72,21 +73,73 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa }) } -export async function addCustomerMatchMembers( +export async function addDeviceMobileIds( request: RequestClient, + settings: AudienceSettings | undefined, + payloads: DeviceIdPayload[], + statsContext?: StatsContext // Adjust type based on actual stats context +) { + if (!settings) { + throw new IntegrationError('No Audience Settings found in payload', 'INVALID_REQUEST_DATA', 400) + } + + const audienceId = payloads[0].external_id + //Format the endpoint + const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' + // Prepare the request payload + const mobileDeviceIdList = { + mobileDeviceIds: [payloads[0].mobileDeviceIds], + consent: { + adUserData: CONSENT_STATUS_GRANTED, + adPersonalization: CONSENT_STATUS_GRANTED + } + } + + // Convert the payload to string if needed + const requestPayload = JSON.stringify({ + advertiserId: settings.advertiserId, + addedMobileDeviceIdList: mobileDeviceIdList + }) + + console.log('MobileDeviceId:', mobileDeviceIdList) + const response = await request(endpoint, { + method: 'POST', + headers: { + authorization: `Bearer ${settings.token}`, + 'Content-Type': 'application/json; charset=utf-8' + }, + body: requestPayload + }) + // Handle the API response generically + if (!response.data || !response.data.firstAndThirdPartyAudienceId) { + statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) + throw new IntegrationError( + `API returned error: ${response.data?.error || 'Unknown error'}`, + 'API_REQUEST_ERROR', + 400 + ) + } + + statsContext?.statsClient?.incr('addCustomerMatchMembers.success', 1, statsContext?.tags) + return response.data +} + +export async function addContactInfo( + request: RequestClient, + settings: AudienceSettings | undefined, payloads: Payload[], statsContext?: StatsContext // Adjust type based on actual stats context ) { console.log('Payload:', payloads) - const settings = {} as AudienceSettings const audienceId = payloads[0].external_id - const endpoint = DV360API + '${audienceId}:editCustomerMatchMembers' + //Format the endpoint + const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' + console.log('endpoint', endpoint) - if (!audienceId) { - throw new IntegrationError('No external ID found in payload', 'INVALID_REQUEST_DATA', 400) + if (!settings) { + throw new IntegrationError('No Audience Settings found in payload', 'INVALID_REQUEST_DATA', 400) } - // Assuming we're dealing with a single payload const payload = payloads[0] // Prepare the request payload @@ -117,19 +170,16 @@ export async function addCustomerMatchMembers( } console.log('Payload Formatted:', requestPayload) + console.log('Token', settings.token) - const response = await request( - endpoint, - { - method: 'POST', - headers: { - authorization: `Bearer ${settings.token}`, - 'Content-Type': 'application/json; charset=utf-8' - }, - body: requestPayload - } - // Handle the API response generically - ) + const response = await request(endpoint, { + method: 'POST', + headers: { + authorization: `Bearer ${settings.token}`, + 'Content-Type': 'application/json; charset=utf-8' + }, + body: requestPayload + }) // Handle the API response generically if (!response.data || !response.data.firstAndThirdPartyAudienceId) { @@ -159,22 +209,22 @@ function processPayload(payload: Payload) { // Normalize and hash only if the value is defined if (payload.emails) { - result.hashedEmail = normalizeAndHash(payload.emails) + result.hashedEmails = normalizeAndHash(payload.emails) } if (payload.phoneNumbers) { - result.hashedPhoneNumber = normalizeAndHash(payload.phoneNumbers) + result.hashedPhoneNumbers = normalizeAndHash(payload.phoneNumbers) } if (payload.zipCodes) { - result.zipCode = normalizeAndHash(payload.zipCodes) + result.zipCodes = payload.zipCodes } if (payload.firstName) { - result.firstName = normalizeAndHash(payload.firstName) + result.hashedFirstName = normalizeAndHash(payload.firstName) } if (payload.lastName) { - result.lastName = normalizeAndHash(payload.lastName) + result.hashedLastName = normalizeAndHash(payload.lastName) } if (payload.countryCode) { - result.countryCode = normalizeAndHash(payload.countryCode) + result.countryCode = payload.countryCode } return result diff --git a/packages/destination-actions/src/destinations/first-party-dv360/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/index.ts index df831dcf02..6c5171a0e6 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/index.ts @@ -1,8 +1,8 @@ import { AudienceDestinationDefinition, IntegrationError } from '@segment/actions-core' import type { AudienceSettings, Settings } from './generated-types' - -import addToList from './addToList' import { createAudienceRequest, getAudienceRequest } from './functions' +import addToAudContactInfo from './addToAudContactInfo' +import addToAudMobileDeviceId from './addToAudMobileDeviceId' const destination: AudienceDestinationDefinition = { name: 'First Party Dv360', @@ -170,7 +170,8 @@ const destination: AudienceDestinationDefinition = { }, actions: { - addToList + addToAudContactInfo, + addToAudMobileDeviceId } } From 5aef1a8a458bc02d5358f206cb2efb99db397c3b Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Mon, 16 Sep 2024 11:49:29 -0700 Subject: [PATCH 08/11] add actions and update functions --- .../first-party-dv360/functions.ts | 12 ++++--- .../destinations/first-party-dv360/index.ts | 6 +++- .../generated-types.ts | 32 +++++++++++++++++++ .../generated-types.ts | 12 +++++++ 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index 8ff0cc0055..08688b9f04 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -73,10 +73,11 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa }) } -export async function addDeviceMobileIds( +export async function editDeviceMobileIds( request: RequestClient, settings: AudienceSettings | undefined, payloads: DeviceIdPayload[], + operation: 'add' | 'remove', statsContext?: StatsContext // Adjust type based on actual stats context ) { if (!settings) { @@ -98,7 +99,8 @@ export async function addDeviceMobileIds( // Convert the payload to string if needed const requestPayload = JSON.stringify({ advertiserId: settings.advertiserId, - addedMobileDeviceIdList: mobileDeviceIdList + ...(operation === 'add' ? { addedMobileDeviceIdLis: mobileDeviceIdList } : {}), + ...(operation === 'remove' ? { removedMobileDeviceIdLis: mobileDeviceIdList } : {}) }) console.log('MobileDeviceId:', mobileDeviceIdList) @@ -124,10 +126,11 @@ export async function addDeviceMobileIds( return response.data } -export async function addContactInfo( +export async function editContactInfo( request: RequestClient, settings: AudienceSettings | undefined, payloads: Payload[], + operation: 'add' | 'remove', statsContext?: StatsContext // Adjust type based on actual stats context ) { console.log('Payload:', payloads) @@ -154,7 +157,8 @@ export async function addContactInfo( // Convert the payload to string if needed const requestPayload = JSON.stringify({ advertiserId: settings.advertiserId, - addedContactInfoList: contactInfoList + ...(operation === 'add' ? { addedContactInfoList: contactInfoList } : {}), + ...(operation === 'remove' ? { removedContactInfoList: contactInfoList } : {}) }) // Ensure the request data size is within acceptable limits diff --git a/packages/destination-actions/src/destinations/first-party-dv360/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/index.ts index 6c5171a0e6..85291edaf9 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/index.ts @@ -1,6 +1,8 @@ import { AudienceDestinationDefinition, IntegrationError } from '@segment/actions-core' import type { AudienceSettings, Settings } from './generated-types' import { createAudienceRequest, getAudienceRequest } from './functions' +import removeFromAudContactInfo from './removeFromAudContactInfo' +import removeFromAudMobileDeviceId from './removeFromAudMobileDeviceId' import addToAudContactInfo from './addToAudContactInfo' import addToAudMobileDeviceId from './addToAudMobileDeviceId' @@ -171,7 +173,9 @@ const destination: AudienceDestinationDefinition = { actions: { addToAudContactInfo, - addToAudMobileDeviceId + addToAudMobileDeviceId, + removeFromAudContactInfo, + removeFromAudMobileDeviceId } } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts new file mode 100644 index 0000000000..7f33875bab --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts @@ -0,0 +1,32 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * A list of the user's emails. If not already hashed, the system will hash them before use. + */ + emails?: string + /** + * A list of the user's phone numbers. If not already hashed, the system will hash them before use. + */ + phoneNumbers?: string + /** + * A list of the user's zip codes. + */ + zipCodes?: string + /** + * The user's first name. If not already hashed, the system will hash it before use. + */ + firstName?: string + /** + * The user's last name. If not already hashed, the system will hash it before use. + */ + lastName?: string + /** + * The country code of the user. + */ + countryCode?: string + /** + * The ID of the DV360 Audience. + */ + external_id?: string +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts new file mode 100644 index 0000000000..bc1f95ce51 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts @@ -0,0 +1,12 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * A list of mobile device IDs defining Customer Match audience members. The size of mobileDeviceIds mustn't be greater than 500,000. + */ + mobileDeviceIds?: string + /** + * The ID of the DV360 Audience. + */ + external_id?: string +} From 42f871d73e8b1371e88b8882bef28a8e6037671f Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Mon, 16 Sep 2024 12:34:18 -0700 Subject: [PATCH 09/11] add actions --- .../addToAudContactInfo/index.ts | 30 +++++++++++++++++++ .../addToAudMobileDeviceId/index.ts | 29 ++++++++++++++++++ .../removeFromAudContactInfo/index.ts | 30 +++++++++++++++++++ .../removeFromAudMobileDeviceId/index.ts | 29 ++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts new file mode 100644 index 0000000000..a74c66bcda --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts @@ -0,0 +1,30 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' +import { editContactInfo } from '../functions' + +const action: ActionDefinition = { + title: 'Edit Customer Match Members - Contact Info List', + description: 'Add or update customer match members in Google Display & Video 360 Contact Info List Audience.', + defaultSubscription: 'event = "Audeince Entered - ContactInfoList"', + fields: { + emails: { ...emails }, + phoneNumbers: { ...phoneNumbers }, + zipCodes: { ...zipCodes }, + firstName: { ...firstName }, + lastName: { ...lastName }, + countryCode: { ...countryCode }, + external_id: { ...external_id } + }, + perform: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editContactInfo(request, audienceSettings, [payload], 'add', statsContext) + }, + performBatch: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editContactInfo(request, audienceSettings, payload, 'add', statsContext) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts new file mode 100644 index 0000000000..cdca6d74f1 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts @@ -0,0 +1,29 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { external_id, mobileDeviceIds } from '../properties' +import { editDeviceMobileIds } from '../functions' + +const action: ActionDefinition = { + title: 'Edit Customer Match Members - Mobile Device Id List', + description: 'Add or update customer match members in Google Display & Video 360 Mobile Device Id List Audience.', + defaultSubscription: 'event = "Audeince Entered - MobileDeviceIdList"', + fields: { + mobileDeviceIds: { ...mobileDeviceIds }, + external_id: { ...external_id } + }, + perform: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + console.log('Pre-Payload', payload) + console.log('Pre-audienceSettings', audienceSettings) + return editDeviceMobileIds(request, audienceSettings, [payload], 'add', statsContext) + }, + performBatch: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + console.log('Pre-Payload', payload) + console.log('Pre-audienceSettings', audienceSettings) + return editDeviceMobileIds(request, audienceSettings, payload, 'add', statsContext) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts new file mode 100644 index 0000000000..25fbba50fb --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts @@ -0,0 +1,30 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' +import { editContactInfo } from '../functions' + +const action: ActionDefinition = { + title: 'Remove Customer Match Members - Contact Info List', + description: 'Remove customer match members in Google Display & Video 360 Contact Info List Audience.', + defaultSubscription: 'event = "Audeince Exited - ContactInfoList"', + fields: { + emails: { ...emails }, + phoneNumbers: { ...phoneNumbers }, + zipCodes: { ...zipCodes }, + firstName: { ...firstName }, + lastName: { ...lastName }, + countryCode: { ...countryCode }, + external_id: { ...external_id } + }, + perform: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editContactInfo(request, audienceSettings, [payload], 'remove', statsContext) + }, + performBatch: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editContactInfo(request, audienceSettings, payload, 'remove', statsContext) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts new file mode 100644 index 0000000000..073c191d81 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts @@ -0,0 +1,29 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { external_id, mobileDeviceIds } from '../properties' +import { editDeviceMobileIds } from '../functions' + +const action: ActionDefinition = { + title: 'Remove Customer Match Members - Mobile Device Id List', + description: 'Remove customer match members in Google Display & Video 360 Mobile Device Id List Audience.', + defaultSubscription: 'event = "Audeince Entered - MobileDeviceIdList"', + fields: { + mobileDeviceIds: { ...mobileDeviceIds }, + external_id: { ...external_id } + }, + perform: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + console.log('Pre-Payload', payload) + console.log('Pre-audienceSettings', audienceSettings) + return editDeviceMobileIds(request, audienceSettings, [payload], 'remove', statsContext) + }, + performBatch: async (request, { payload, statsContext, audienceSettings }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + console.log('Pre-Payload', payload) + console.log('Pre-audienceSettings', audienceSettings) + return editDeviceMobileIds(request, audienceSettings, payload, 'remove', statsContext) + } +} + +export default action From ef69efe9fd0a1f2857a2036dd2990aadbc100891 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Wed, 16 Oct 2024 10:53:15 -0400 Subject: [PATCH 10/11] added unit tests and updated dv360 actions --- .../first-party-dv360/_tests_/index.test.ts | 269 ++++++++++++++++++ .../addToAudContactInfo/generated-types.ts | 4 + .../addToAudContactInfo/index.ts | 17 +- .../addToAudMobileDeviceId/generated-types.ts | 4 + .../addToAudMobileDeviceId/index.ts | 19 +- .../first-party-dv360/functions.ts | 64 +---- .../first-party-dv360/properties.ts | 41 ++- .../generated-types.ts | 4 + .../removeFromAudContactInfo/index.ts | 15 +- .../generated-types.ts | 4 + .../removeFromAudMobileDeviceId/index.ts | 19 +- 11 files changed, 364 insertions(+), 96 deletions(-) create mode 100644 packages/destination-actions/src/destinations/first-party-dv360/_tests_/index.test.ts diff --git a/packages/destination-actions/src/destinations/first-party-dv360/_tests_/index.test.ts b/packages/destination-actions/src/destinations/first-party-dv360/_tests_/index.test.ts new file mode 100644 index 0000000000..b5af4e4cfa --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/_tests_/index.test.ts @@ -0,0 +1,269 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration, IntegrationError } from '@segment/actions-core' +import Destination from '../index' + +const audienceName = 'Test Audience' +const testDestination = createTestIntegration(Destination) + +const createAudienceInput = { + settings: {}, + audienceName: audienceName, + audienceSettings: { // Using audienceSettings as specified + advertiserId: '12345', + audienceType: 'CUSTOMER_MATCH_CONTACT_INFO', + membershipDurationDays: '30', + token: 'temp-token', + description: 'Test description', + }, +} + +const getAudienceInput = { + settings: {}, + audienceSettings: { + advertiserId: '12345', + token: 'temp-token', + }, + externalId: 'audience-id-123', +} + +//Create Audience Tests + +describe('Audience Destination', () => { + describe('createAudience', () => { + it('creates an audience successfully', async () => { + nock('https://displayvideo.googleapis.com') + .post('/v3/firstAndThirdPartyAudiences?advertiserId=12345', { + displayName: audienceName, + audienceType: 'CUSTOMER_MATCH_CONTACT_INFO', + membershipDurationDays: '30', + description: 'Test description', + audienceSource: 'AUDIENCE_SOURCE_UNSPECIFIED', + firstAndThirdPartyAudienceType: 'FIRST_AND_THIRD_PARTY_AUDIENCE_TYPE_FIRST_PARTY', + }) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { firstAndThirdPartyAudienceId: 'audience-id-123' }) + + const result = await testDestination.createAudience(createAudienceInput) + expect(result).toEqual({ externalId: 'audience-id-123' }) + }) + + it('errors out when no audience name is provided', async () => { + createAudienceInput.audienceName = '' + + await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError) + }) + + it('errors out when no advertiser ID is provided', async () => { + createAudienceInput.audienceSettings.advertiserId = '' + + await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError) + }) + + it('errors out when no audience type is provided', async () => { + createAudienceInput.audienceSettings.audienceType = '' + + await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError) + }) + + it('errors out when no membership duration days is provided', async () => { + createAudienceInput.audienceSettings.membershipDurationDays = '' + + await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError) + }) + }) + + //Get Audience Tests + + describe('getAudience', () => { + it('should succeed when provided with a valid audience ID', async () => { + nock('https://displayvideo.googleapis.com') + .get(`/v3/firstAndThirdPartyAudiences/audience-id-123?advertiserId=12345`) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { + firstAndThirdPartyAudienceId: 'audience-id-123', + }) + + const result = await testDestination.getAudience(getAudienceInput) + expect(result).toEqual({ externalId: 'audience-id-123' }) + }) + + it('should fail when the audience ID is missing', async () => { + const missingIdInput = { + ...getAudienceInput, + externalId: '', // Simulate missing audience ID + } + + await expect(testDestination.getAudience(missingIdInput)).rejects.toThrowError( + new IntegrationError('Failed to retrieve audience ID value', 'MISSING_REQUIRED_FIELD', 400) + ) + }) + }) +}) + +//Payloads for editing customer match list + +const payloadContactInfo = { + emails: 'test@gmail.com', + phoneNumbers: '1234567890', + zipCodes: '12345', + firstName: 'John', + lastName: 'Doe', + countryCode: '+1' +} + +const payloadDeviceId = { + mobileDeviceIds: '123' +} + +//Edit Customer Match Members - Contact Info List + +describe('Edit Customer Match Members - Contact Info List', () => { + const event = createTestEvent({ + event: 'Audience Entered', + type: 'track', + properties: {}, + context: { + traits: payloadContactInfo, + personas: { + external_audience_id: 'audience-id-123', + audience_settings: { + advertiserId: "12345", + token: 'temp-token' + } + } + }, + }) + it('should add customer match members successfully', async () => { + nock('https://displayvideo.googleapis.com') + .post('/v3/firstAndThirdPartyAudiences/audience-id-123:editCustomerMatchMembers', { + advertiserId: '12345', + addedContactInfoList: { + contactInfos: [{ + hashedEmails: '87924606b4131a8aceeeae8868531fbb9712aaa07a5d3a756b26ce0f5d6ca674', + hashedPhoneNumbers: 'c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646', + zipCodes: '12345', + hashedFirstName: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', + hashedLastName: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f', + countryCode: '+1', + }], + consent: { + adUserData: 'CONSENT_STATUS_GRANTED', + adPersonalization: 'CONSENT_STATUS_GRANTED' + } + } + }) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { firstAndThirdPartyAudienceId: 'audience-id-123' }); + const result = await testDestination.testAction('addToAudContactInfo', { + event, + useDefaultMappings: true + }) + expect(result).toContainEqual(expect.objectContaining({ + data: expect.objectContaining({ + firstAndThirdPartyAudienceId: 'audience-id-123', + }) + })) + }) + it('should remove customer match members successfully', async () => { + nock('https://displayvideo.googleapis.com') + .post('/v3/firstAndThirdPartyAudiences/audience-id-123:editCustomerMatchMembers', { + advertiserId: '12345', + removedContactInfoList: { + contactInfos: [{ + hashedEmails: '87924606b4131a8aceeeae8868531fbb9712aaa07a5d3a756b26ce0f5d6ca674', + hashedPhoneNumbers: 'c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646', + zipCodes: '12345', + hashedFirstName: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', + hashedLastName: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f', + countryCode: '+1', + }], + consent: { + adUserData: 'CONSENT_STATUS_GRANTED', + adPersonalization: 'CONSENT_STATUS_GRANTED' + } + } + }) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { firstAndThirdPartyAudienceId: 'audience-id-123' }); + const result = await testDestination.testAction('removeFromAudContactInfo', { + event, + useDefaultMappings: true + }) + expect(result).toContainEqual(expect.objectContaining({ + data: expect.objectContaining({ + firstAndThirdPartyAudienceId: 'audience-id-123', + }) + })) + }) + +}) + +//Edit Customer Match Members - Mobile Device ID List + +describe('Edit Customer Match Members - Mobile Device ID List', () => { + const event = createTestEvent({ + event: 'Audience Entered', + type: 'track', + properties: {}, + context: { + traits: payloadDeviceId, + personas: { + external_audience_id: 'audience-id-123', + audience_settings: { + advertiserId: "12345", + token: 'temp-token' + } + } + }, + }) + it('should remove customer match members successfully', async () => { + nock('https://displayvideo.googleapis.com') + .post('/v3/firstAndThirdPartyAudiences/audience-id-123:editCustomerMatchMembers', { + advertiserId: '12345', + addedMobileDeviceIdList: { + mobileDeviceIds: ['123'], + consent: { + adUserData: 'CONSENT_STATUS_GRANTED', + adPersonalization: 'CONSENT_STATUS_GRANTED' + } + } + }) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { firstAndThirdPartyAudienceId: 'audience-id-123' }); + console.log("event:", event) + const result = await testDestination.testAction('addToAudMobileDeviceId', { + event, + useDefaultMappings: true + }) + expect(result).toContainEqual(expect.objectContaining({ + data: expect.objectContaining({ + firstAndThirdPartyAudienceId: 'audience-id-123', + }) + })) + }) + it('should remove customer match members successfully', async () => { + nock('https://displayvideo.googleapis.com') + .post('/v3/firstAndThirdPartyAudiences/audience-id-123:editCustomerMatchMembers', { + advertiserId: '12345', + removedMobileDeviceIdList: { + mobileDeviceIds: ['123'], + consent: { + adUserData: 'CONSENT_STATUS_GRANTED', + adPersonalization: 'CONSENT_STATUS_GRANTED' + } + } + }) + .matchHeader('Authorization', 'Bearer temp-token') + .reply(200, { firstAndThirdPartyAudienceId: 'audience-id-123' }); + const result = await testDestination.testAction('removeFromAudMobileDeviceId', { + event, + useDefaultMappings: true + }) + expect(result).toContainEqual(expect.objectContaining({ + data: expect.objectContaining({ + firstAndThirdPartyAudienceId: 'audience-id-123', + }) + })) + }) + +}) \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts index 7f33875bab..e667a91db5 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts @@ -29,4 +29,8 @@ export interface Payload { * The ID of the DV360 Audience. */ external_id?: string + /** + * The Advertiser ID associated with the DV360 Audience. + */ + advertiser_id?: string } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts index a74c66bcda..d985694783 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts @@ -1,13 +1,13 @@ import type { ActionDefinition } from '@segment/actions-core' import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' -import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id, advertiser_id } from '../properties' import { editContactInfo } from '../functions' const action: ActionDefinition = { title: 'Edit Customer Match Members - Contact Info List', description: 'Add or update customer match members in Google Display & Video 360 Contact Info List Audience.', - defaultSubscription: 'event = "Audeince Entered - ContactInfoList"', + defaultSubscription: 'event = "Audience Entered"', fields: { emails: { ...emails }, phoneNumbers: { ...phoneNumbers }, @@ -15,16 +15,17 @@ const action: ActionDefinition = { firstName: { ...firstName }, lastName: { ...lastName }, countryCode: { ...countryCode }, - external_id: { ...external_id } + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } }, - perform: async (request, { payload, statsContext, audienceSettings }) => { + perform: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - return editContactInfo(request, audienceSettings, [payload], 'add', statsContext) + return editContactInfo(request, [payload], 'add', statsContext) }, - performBatch: async (request, { payload, statsContext, audienceSettings }) => { + performBatch: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) - return editContactInfo(request, audienceSettings, payload, 'add', statsContext) + return editContactInfo(request, payload, 'add', statsContext) } } -export default action +export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts index bc1f95ce51..2a5cd96489 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts @@ -9,4 +9,8 @@ export interface Payload { * The ID of the DV360 Audience. */ external_id?: string + /** + * The Advertiser ID associated with the DV360 Audience. + */ + advertiser_id?: string } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts index cdca6d74f1..03a6ae3f41 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts @@ -1,28 +1,25 @@ import type { ActionDefinition } from '@segment/actions-core' import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' -import { external_id, mobileDeviceIds } from '../properties' +import { advertiser_id, external_id, mobileDeviceIds } from '../properties' import { editDeviceMobileIds } from '../functions' const action: ActionDefinition = { title: 'Edit Customer Match Members - Mobile Device Id List', description: 'Add or update customer match members in Google Display & Video 360 Mobile Device Id List Audience.', - defaultSubscription: 'event = "Audeince Entered - MobileDeviceIdList"', + defaultSubscription: 'event = "Audience Entered', fields: { mobileDeviceIds: { ...mobileDeviceIds }, - external_id: { ...external_id } + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } }, - perform: async (request, { payload, statsContext, audienceSettings }) => { + perform: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - console.log('Pre-Payload', payload) - console.log('Pre-audienceSettings', audienceSettings) - return editDeviceMobileIds(request, audienceSettings, [payload], 'add', statsContext) + return editDeviceMobileIds(request, [payload], 'add', statsContext) }, - performBatch: async (request, { payload, statsContext, audienceSettings }) => { + performBatch: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) - console.log('Pre-Payload', payload) - console.log('Pre-audienceSettings', audienceSettings) - return editDeviceMobileIds(request, audienceSettings, payload, 'add', statsContext) + return editDeviceMobileIds(request, payload, 'add', statsContext) } } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts index 08688b9f04..97a34bde8f 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/functions.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/functions.ts @@ -5,7 +5,6 @@ import { createHash } from 'crypto' import { Payload as DeviceIdPayload } from './addToAudMobileDeviceId/generated-types' const DV360API = `https://displayvideo.googleapis.com/v3/firstAndThirdPartyAudiences` -const MAX_REQUEST_SIZE = 500000 const CONSENT_STATUS_GRANTED = 'CONSENT_STATUS_GRANTED' // Define consent status interface createAudienceRequestParams { @@ -75,21 +74,17 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa export async function editDeviceMobileIds( request: RequestClient, - settings: AudienceSettings | undefined, payloads: DeviceIdPayload[], operation: 'add' | 'remove', statsContext?: StatsContext // Adjust type based on actual stats context ) { - if (!settings) { - throw new IntegrationError('No Audience Settings found in payload', 'INVALID_REQUEST_DATA', 400) - } - - const audienceId = payloads[0].external_id + const payload = payloads[0] + const audienceId = payload.external_id //Format the endpoint const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' // Prepare the request payload const mobileDeviceIdList = { - mobileDeviceIds: [payloads[0].mobileDeviceIds], + mobileDeviceIds: [payload.mobileDeviceIds], consent: { adUserData: CONSENT_STATUS_GRANTED, adPersonalization: CONSENT_STATUS_GRANTED @@ -98,21 +93,18 @@ export async function editDeviceMobileIds( // Convert the payload to string if needed const requestPayload = JSON.stringify({ - advertiserId: settings.advertiserId, - ...(operation === 'add' ? { addedMobileDeviceIdLis: mobileDeviceIdList } : {}), - ...(operation === 'remove' ? { removedMobileDeviceIdLis: mobileDeviceIdList } : {}) + advertiserId: payload.advertiser_id, + ...(operation === 'add' ? { addedMobileDeviceIdList: mobileDeviceIdList } : {}), + ...(operation === 'remove' ? { removedMobileDeviceIdList: mobileDeviceIdList } : {}) }) - - console.log('MobileDeviceId:', mobileDeviceIdList) const response = await request(endpoint, { method: 'POST', headers: { - authorization: `Bearer ${settings.token}`, + authorization: 'Bearer temp-token', 'Content-Type': 'application/json; charset=utf-8' }, body: requestPayload }) - // Handle the API response generically if (!response.data || !response.data.firstAndThirdPartyAudienceId) { statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) throw new IntegrationError( @@ -128,22 +120,15 @@ export async function editDeviceMobileIds( export async function editContactInfo( request: RequestClient, - settings: AudienceSettings | undefined, payloads: Payload[], operation: 'add' | 'remove', - statsContext?: StatsContext // Adjust type based on actual stats context + statsContext?: StatsContext ) { - console.log('Payload:', payloads) + const payload = payloads[0] const audienceId = payloads[0].external_id + //Format the endpoint const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' - console.log('endpoint', endpoint) - - if (!settings) { - throw new IntegrationError('No Audience Settings found in payload', 'INVALID_REQUEST_DATA', 400) - } - - const payload = payloads[0] // Prepare the request payload const contactInfoList = { @@ -156,45 +141,20 @@ export async function editContactInfo( // Convert the payload to string if needed const requestPayload = JSON.stringify({ - advertiserId: settings.advertiserId, + advertiserId: payload.advertiser_id, ...(operation === 'add' ? { addedContactInfoList: contactInfoList } : {}), ...(operation === 'remove' ? { removedContactInfoList: contactInfoList } : {}) }) - // Ensure the request data size is within acceptable limits - console.log('FIX') - const requestSize = Buffer.byteLength(requestPayload, 'utf8') - if (requestSize > MAX_REQUEST_SIZE) { - statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) - throw new IntegrationError( - `Request data size exceeds limit of ${MAX_REQUEST_SIZE} bytes`, - 'INVALID_REQUEST_DATA', - 400 - ) - } - - console.log('Payload Formatted:', requestPayload) - console.log('Token', settings.token) - const response = await request(endpoint, { method: 'POST', headers: { - authorization: `Bearer ${settings.token}`, + authorization: 'Bearer temp-token', 'Content-Type': 'application/json; charset=utf-8' }, body: requestPayload }) - // Handle the API response generically - if (!response.data || !response.data.firstAndThirdPartyAudienceId) { - statsContext?.statsClient?.incr('addCustomerMatchMembers.error', 1, statsContext?.tags) - throw new IntegrationError( - `API returned error: ${response.data?.error || 'Unknown error'}`, - 'API_REQUEST_ERROR', - 400 - ) - } - statsContext?.statsClient?.incr('addCustomerMatchMembers.success', 1, statsContext?.tags) return response.data } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/properties.ts b/packages/destination-actions/src/destinations/first-party-dv360/properties.ts index a54f768f84..4f9af99144 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/properties.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/properties.ts @@ -10,12 +10,22 @@ export const external_id: InputField = { unsafe_hidden: true } +export const advertiser_id: InputField = { + label: 'Advertiser ID', + description: 'The Advertiser ID associated with the DV360 Audience.', + type: 'string', + default: { + '@path': '$.context.personas.audience_settings.advertiserId' + }, + unsafe_hidden: true +} + export const emails: InputField = { label: 'Emails', description: `A list of the user's emails. If not already hashed, the system will hash them before use.`, type: 'string', default: { - '@path': '$.properties.emails' + '@path': '$.context.traits.emails' } } @@ -24,7 +34,7 @@ export const phoneNumbers: InputField = { description: `A list of the user's phone numbers. If not already hashed, the system will hash them before use.`, type: 'string', default: { - '@path': '$.properties.phoneNumbers' + '@path': '$.context.traits.phoneNumbers' } } @@ -33,7 +43,7 @@ export const zipCodes: InputField = { description: `A list of the user's zip codes.`, type: 'string', default: { - '@path': '$.properties.zipCodes' + '@path': '$.context.traits.zipCodes' } } @@ -42,7 +52,7 @@ export const firstName: InputField = { description: `The user's first name. If not already hashed, the system will hash it before use.`, type: 'string', default: { - '@path': '$.properties.firstName' + '@path': '$.context.traits.firstName' } } @@ -51,7 +61,7 @@ export const lastName: InputField = { description: `The user's last name. If not already hashed, the system will hash it before use.`, type: 'string', default: { - '@path': '$.properties.lastName' + '@path': '$.context.traits.lastName' } } @@ -60,7 +70,7 @@ export const countryCode: InputField = { description: `The country code of the user.`, type: 'string', default: { - '@path': '$.properties.countryCode' + '@path': '$.context.traits..countryCode' } } @@ -69,6 +79,23 @@ export const mobileDeviceIds: InputField = { description: `A list of mobile device IDs defining Customer Match audience members. The size of mobileDeviceIds mustn't be greater than 500,000.`, type: 'string', default: { - '@path': '$.properties.mobileDeviceIds' + '@path': '$.context.traits.mobileDeviceIds' } } +export const enable_batching: InputField = { + label: 'Enable Batching', + description: 'Enable batching of requests.', + type: 'boolean', + default: true, + unsafe_hidden: true, + required: true +} + +export const batch_size: InputField = { + label: 'Batch Size', + description: 'Maximum number of events to include in each batch. Actual batch sizes may be lower.', + type: 'number', + default: 500000, + unsafe_hidden: true, + required: true +} diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts index 7f33875bab..e667a91db5 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts @@ -29,4 +29,8 @@ export interface Payload { * The ID of the DV360 Audience. */ external_id?: string + /** + * The Advertiser ID associated with the DV360 Audience. + */ + advertiser_id?: string } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts index 25fbba50fb..b20eecd9ab 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts @@ -1,13 +1,13 @@ import type { ActionDefinition } from '@segment/actions-core' import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' -import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id } from '../properties' +import { emails, phoneNumbers, zipCodes, firstName, lastName, countryCode, external_id, advertiser_id } from '../properties' import { editContactInfo } from '../functions' const action: ActionDefinition = { title: 'Remove Customer Match Members - Contact Info List', description: 'Remove customer match members in Google Display & Video 360 Contact Info List Audience.', - defaultSubscription: 'event = "Audeince Exited - ContactInfoList"', + defaultSubscription: 'event = "Audience Exited"', fields: { emails: { ...emails }, phoneNumbers: { ...phoneNumbers }, @@ -15,15 +15,16 @@ const action: ActionDefinition = { firstName: { ...firstName }, lastName: { ...lastName }, countryCode: { ...countryCode }, - external_id: { ...external_id } + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } }, - perform: async (request, { payload, statsContext, audienceSettings }) => { + perform: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - return editContactInfo(request, audienceSettings, [payload], 'remove', statsContext) + return editContactInfo(request, [payload], 'remove', statsContext) }, - performBatch: async (request, { payload, statsContext, audienceSettings }) => { + performBatch: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) - return editContactInfo(request, audienceSettings, payload, 'remove', statsContext) + return editContactInfo(request, payload, 'remove', statsContext) } } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts index bc1f95ce51..2a5cd96489 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts @@ -9,4 +9,8 @@ export interface Payload { * The ID of the DV360 Audience. */ external_id?: string + /** + * The Advertiser ID associated with the DV360 Audience. + */ + advertiser_id?: string } diff --git a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts index 073c191d81..137f4a2177 100644 --- a/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts @@ -1,28 +1,25 @@ import type { ActionDefinition } from '@segment/actions-core' import type { AudienceSettings, Settings } from '../generated-types' import type { Payload } from './generated-types' -import { external_id, mobileDeviceIds } from '../properties' +import { advertiser_id, external_id, mobileDeviceIds } from '../properties' import { editDeviceMobileIds } from '../functions' const action: ActionDefinition = { title: 'Remove Customer Match Members - Mobile Device Id List', description: 'Remove customer match members in Google Display & Video 360 Mobile Device Id List Audience.', - defaultSubscription: 'event = "Audeince Entered - MobileDeviceIdList"', + defaultSubscription: 'event = "Audience Entered', fields: { mobileDeviceIds: { ...mobileDeviceIds }, - external_id: { ...external_id } + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } }, - perform: async (request, { payload, statsContext, audienceSettings }) => { + perform: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) - console.log('Pre-Payload', payload) - console.log('Pre-audienceSettings', audienceSettings) - return editDeviceMobileIds(request, audienceSettings, [payload], 'remove', statsContext) + return editDeviceMobileIds(request, [payload], 'remove', statsContext) }, - performBatch: async (request, { payload, statsContext, audienceSettings }) => { + performBatch: async (request, { payload, statsContext }) => { statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) - console.log('Pre-Payload', payload) - console.log('Pre-audienceSettings', audienceSettings) - return editDeviceMobileIds(request, audienceSettings, payload, 'remove', statsContext) + return editDeviceMobileIds(request, payload, 'remove', statsContext) } } From c6bbd0b77056473816b13c48e1aa6d269c80d135 Mon Sep 17 00:00:00 2001 From: Rajul Vadera Date: Wed, 16 Oct 2024 11:13:25 -0400 Subject: [PATCH 11/11] fix linter error --- .../destinations/display-video-360/combine.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/destination-actions/src/destinations/display-video-360/combine.py diff --git a/packages/destination-actions/src/destinations/display-video-360/combine.py b/packages/destination-actions/src/destinations/display-video-360/combine.py new file mode 100644 index 0000000000..6873ad9940 --- /dev/null +++ b/packages/destination-actions/src/destinations/display-video-360/combine.py @@ -0,0 +1,18 @@ +import pandas as pd + +# Load the CSV files +file1 = 'Documents/Google Conversion IDs Sept 2024.csv' +file2 = 'Downloads/Rajul Query.csv' + +# Read the CSV files into DataFrames +df1 = pd.read_csv(file1) +df2 = pd.read_csv(file2) + +# Combine the DataFrames based on a common column (e.g., 'id') +# You can use 'inner', 'outer', 'left', or 'right' joins based on your needs +combined_df = pd.merge(df1, df2, on='SOURCE_ID', how='inner') + +# Save the combined DataFrame to a new CSV file +combined_df.to_csv('combined_file.csv', index=False) + +print("Files combined successfully!")