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/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!") 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 new file mode 100644 index 0000000000..e667a91db5 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/generated-types.ts @@ -0,0 +1,36 @@ +// 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 + /** + * 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 new file mode 100644 index 0000000000..d985694783 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudContactInfo/index.ts @@ -0,0 +1,31 @@ +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, 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 = "Audience Entered"', + fields: { + emails: { ...emails }, + phoneNumbers: { ...phoneNumbers }, + zipCodes: { ...zipCodes }, + firstName: { ...firstName }, + lastName: { ...lastName }, + countryCode: { ...countryCode }, + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } + }, + perform: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editContactInfo(request, [payload], 'add', statsContext) + }, + performBatch: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editContactInfo(request, payload, 'add', statsContext) + } +} + +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 new file mode 100644 index 0000000000..2a5cd96489 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/generated-types.ts @@ -0,0 +1,16 @@ +// 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 + /** + * 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 new file mode 100644 index 0000000000..03a6ae3f41 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/addToAudMobileDeviceId/index.ts @@ -0,0 +1,26 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +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 = "Audience Entered', + fields: { + mobileDeviceIds: { ...mobileDeviceIds }, + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } + }, + perform: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editDeviceMobileIds(request, [payload], 'add', statsContext) + }, + performBatch: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editDeviceMobileIds(request, payload, 'add', statsContext) + } +} + +export default action 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 deleted file mode 100644 index 944d22b085..0000000000 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/generated-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Generated file. DO NOT MODIFY IT BY HAND. - -export interface Payload {} 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 811316a20c..0000000000 --- a/packages/destination-actions/src/destinations/first-party-dv360/addToList/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ActionDefinition } from '@segment/actions-core' -import type { Settings } from '../generated-types' -import type { Payload } from './generated-types' - -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 - // }) - } -} - -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 1c7f12b5e8..35973f5085 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 './addToAudContactInfo/generated-types' +import { createHash } from 'crypto' +import { Payload as DeviceIdPayload } from './addToAudMobileDeviceId/generated-types' const DV360API = `https://displayvideo.googleapis.com/v3/firstAndThirdPartyAudiences` +const CONSENT_STATUS_GRANTED = 'CONSENT_STATUS_GRANTED' // Define consent status interface createAudienceRequestParams { advertiserId: string @@ -18,6 +23,16 @@ interface getAudienceParams { token?: string } +interface DV360editCustomerMatchResponse { + firstAndThirdPartyAudienceId: string + error: [ + { + code: string + message: string + status: string + } + ] +} export const createAudienceRequest = ( request: RequestClient, params: createAudienceRequestParams @@ -57,3 +72,125 @@ export const getAudienceRequest = (request: RequestClient, params: getAudiencePa } }) } + +export async function editDeviceMobileIds( + request: RequestClient, + payloads: DeviceIdPayload[], + operation: 'add' | 'remove', + statsContext?: StatsContext // Adjust type based on actual stats context +) { + const payload = payloads[0] + const audienceId = payload.external_id + //Format the endpoint + const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' + // Prepare the request payload + const mobileDeviceIdList = { + mobileDeviceIds: [payload.mobileDeviceIds], + consent: { + adUserData: CONSENT_STATUS_GRANTED, + adPersonalization: CONSENT_STATUS_GRANTED + } + } + + // Convert the payload to string if needed + const requestPayload = JSON.stringify({ + advertiserId: payload.advertiser_id, + ...(operation === 'add' ? { addedMobileDeviceIdList: mobileDeviceIdList } : {}), + ...(operation === 'remove' ? { removedMobileDeviceIdList: mobileDeviceIdList } : {}) + }) + const response = await request(endpoint, { + method: 'POST', + headers: { + authorization: 'Bearer temp-token', + 'Content-Type': 'application/json; charset=utf-8' + }, + body: requestPayload + }) + 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 editContactInfo( + request: RequestClient, + payloads: Payload[], + operation: 'add' | 'remove', + statsContext?: StatsContext +) { + const payload = payloads[0] + const audienceId = payloads[0].external_id + + //Format the endpoint + const endpoint = DV360API + '/' + audienceId + ':editCustomerMatchMembers' + + // 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: payload.advertiser_id, + ...(operation === 'add' ? { addedContactInfoList: contactInfoList } : {}), + ...(operation === 'remove' ? { removedContactInfoList: contactInfoList } : {}) + }) + + const response = await request(endpoint, { + method: 'POST', + headers: { + authorization: 'Bearer temp-token', + 'Content-Type': 'application/json; charset=utf-8' + }, + body: requestPayload + }) + + 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.hashedEmails = normalizeAndHash(payload.emails) + } + if (payload.phoneNumbers) { + result.hashedPhoneNumbers = normalizeAndHash(payload.phoneNumbers) + } + if (payload.zipCodes) { + result.zipCodes = payload.zipCodes + } + if (payload.firstName) { + result.hashedFirstName = normalizeAndHash(payload.firstName) + } + if (payload.lastName) { + result.hashedLastName = normalizeAndHash(payload.lastName) + } + if (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 7f2115234b..823da9cafe 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,10 @@ import { AudienceDestinationDefinition, IntegrationError } from '@segment/actions-core' import type { AudienceSettings, Settings } from './generated-types' - -import addToList from './addToList' import { createAudienceRequest, getAudienceRequest } from './functions' +import removeFromAudContactInfo from './removeFromAudContactInfo' +import removeFromAudMobileDeviceId from './removeFromAudMobileDeviceId' +import addToAudContactInfo from './addToAudContactInfo' +import addToAudMobileDeviceId from './addToAudMobileDeviceId' const destination: AudienceDestinationDefinition = { name: 'First Party Dv360', @@ -179,7 +181,10 @@ const destination: AudienceDestinationDefinition = { }, actions: { - addToList + addToAudContactInfo, + addToAudMobileDeviceId, + removeFromAudContactInfo, + removeFromAudMobileDeviceId } } 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..4f9af99144 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/properties.ts @@ -0,0 +1,101 @@ +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 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': '$.context.traits.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': '$.context.traits.phoneNumbers' + } +} + +export const zipCodes: InputField = { + label: 'ZIP Codes', + description: `A list of the user's zip codes.`, + type: 'string', + default: { + '@path': '$.context.traits.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': '$.context.traits.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': '$.context.traits.lastName' + } +} + +export const countryCode: InputField = { + label: 'Country Code', + description: `The country code of the user.`, + type: 'string', + default: { + '@path': '$.context.traits..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': '$.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 new file mode 100644 index 0000000000..e667a91db5 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/generated-types.ts @@ -0,0 +1,36 @@ +// 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 + /** + * 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 new file mode 100644 index 0000000000..b20eecd9ab --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudContactInfo/index.ts @@ -0,0 +1,31 @@ +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, 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 = "Audience Exited"', + fields: { + emails: { ...emails }, + phoneNumbers: { ...phoneNumbers }, + zipCodes: { ...zipCodes }, + firstName: { ...firstName }, + lastName: { ...lastName }, + countryCode: { ...countryCode }, + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } + }, + perform: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editContactInfo(request, [payload], 'remove', statsContext) + }, + performBatch: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editContactInfo(request, payload, 'remove', statsContext) + } +} + +export default action 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..2a5cd96489 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/generated-types.ts @@ -0,0 +1,16 @@ +// 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 + /** + * 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 new file mode 100644 index 0000000000..137f4a2177 --- /dev/null +++ b/packages/destination-actions/src/destinations/first-party-dv360/removeFromAudMobileDeviceId/index.ts @@ -0,0 +1,26 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { AudienceSettings, Settings } from '../generated-types' +import type { Payload } from './generated-types' +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 = "Audience Entered', + fields: { + mobileDeviceIds: { ...mobileDeviceIds }, + external_id: { ...external_id }, + advertiser_id: { ...advertiser_id } + }, + perform: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers', 1, statsContext?.tags) + return editDeviceMobileIds(request, [payload], 'remove', statsContext) + }, + performBatch: async (request, { payload, statsContext }) => { + statsContext?.statsClient?.incr('editCustomerMatchMembers.batch', 1, statsContext?.tags) + return editDeviceMobileIds(request, payload, 'remove', statsContext) + } +} + +export default action