diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts b/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts index 618730fa59d..5246a144bca 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts @@ -2,15 +2,29 @@ import { PINTEREST_CONVERSIONS_API_VERSION } from './versioning-info' export const API_VERSION = PINTEREST_CONVERSIONS_API_VERSION export const EVENT_NAME = { + ADD_PAYMENT_INFO: 'add_payment_info', ADD_TO_CART: 'add_to_cart', + ADD_TO_WISHLIST: 'add_to_wishlist', + APP_INSTALL: 'app_install', + APP_OPEN: 'app_open', CHECKOUT: 'checkout', + CONTACT: 'contact', CUSTOM: 'custom', + CUSTOMIZE_PRODUCT: 'customize_product', + FIND_LOCATION: 'find_location', + INITIATE_CHECKOUT: 'initiate_checkout', LEAD: 'lead', PAGE_VISIT: 'page_visit', + SCHEDULE: 'schedule', SEARCH: 'search', - SIGNUP: 'search', + SIGNUP: 'signup', + START_TRIAL: 'start_trial', + SUBMIT_APPLICATION: 'submit_application', + SUBSCRIBE: 'subscribe', VIEW_CATEGORY: 'view_category', - WATCH_VIDEO: 'watch_video' + VIEW_CONTENT: 'view_content', + WATCH_VIDEO: 'watch_video', + } export const ACTION_SOURCE = ['app_android', 'app_ios', 'web', 'offline'] export const PARTNER_NAME = 'ss-segment' diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/index.ts b/packages/destination-actions/src/destinations/pinterest-conversions/index.ts index f8e574de627..01deef2a545 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/index.ts @@ -1,5 +1,5 @@ import { defaultValues, DestinationDefinition } from '@segment/actions-core' -import { API_VERSION } from './constants' +import { API_VERSION, EVENT_NAME } from './constants' import type { Settings } from './generated-types' import reportConversionEvent from './reportConversionEvent' import type { PinterestConversionsTestAuthenticationError } from './types' @@ -66,23 +66,73 @@ const destination: DestinationDefinition = { } }, presets: [ + { + name: 'Add Payment Info', + subscribe: 'type = "track" AND event = "Payment Info Entered"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.ADD_PAYMENT_INFO + }, + type: 'automatic' + }, { name: 'Add to Cart', subscribe: 'type = "track" AND event = "Product Added"', partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'add_to_cart' + event_name: EVENT_NAME.ADD_TO_CART + }, + type: 'automatic' + }, + { + name: 'Add to Wishlist', + subscribe: 'type = "track" AND event = "Product Added to Wishlist"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.ADD_TO_WISHLIST + }, + type: 'automatic' + }, + { + name: 'App Install', + subscribe: 'type = "track" AND event = "Application Installed"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.APP_INSTALL + }, + type: 'automatic' + }, + { + name: 'App Open', + subscribe: 'type = "track" AND event = "Application Opened"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.APP_OPEN }, type: 'automatic' }, { name: 'Checkout', - subscribe: 'type = "track" AND event = "Checkout"', + subscribe: 'type = "track" AND event = "Order Completed"', partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'checkout' + event_name: EVENT_NAME.CHECKOUT + }, + type: 'automatic' + }, + { + name: 'Initiate Checkout', + subscribe: 'type = "track" AND event = "Checkout Started"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.INITIATE_CHECKOUT }, type: 'automatic' }, @@ -92,7 +142,7 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'lead' + event_name: EVENT_NAME.LEAD }, type: 'automatic' }, @@ -102,7 +152,7 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'page_visit' + event_name: EVENT_NAME.PAGE_VISIT }, type: 'automatic' }, @@ -112,7 +162,7 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'search' + event_name: EVENT_NAME.SEARCH }, type: 'automatic' }, @@ -122,7 +172,27 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'signup' + event_name: EVENT_NAME.SIGNUP + }, + type: 'automatic' + }, + { + name: 'Start Trial', + subscribe: 'type = "track" AND event = "Trial Started"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.START_TRIAL + }, + type: 'automatic' + }, + { + name: 'Subscribe', + subscribe: 'type = "track" AND event = "Subscription Created"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.SUBSCRIBE }, type: 'automatic' }, @@ -132,7 +202,17 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'view_category' + event_name: EVENT_NAME.VIEW_CATEGORY + }, + type: 'automatic' + }, + { + name: 'View Content', + subscribe: 'type = "track" AND event = "Product Viewed"', + partnerAction: 'reportConversionEvent', + mapping: { + ...defaultValues(reportConversionEvent.fields), + event_name: EVENT_NAME.VIEW_CONTENT }, type: 'automatic' }, @@ -142,7 +222,7 @@ const destination: DestinationDefinition = { partnerAction: 'reportConversionEvent', mapping: { ...defaultValues(reportConversionEvent.fields), - event_name: 'watch_video' + event_name: EVENT_NAME.WATCH_VIDEO }, type: 'automatic' } diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/pinterest-capi-custom-data.ts b/packages/destination-actions/src/destinations/pinterest-conversions/pinterest-capi-custom-data.ts index 00560fafb83..cecf9087e58 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/pinterest-capi-custom-data.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/pinterest-capi-custom-data.ts @@ -1,9 +1,10 @@ -import { InputField } from '@segment/actions-core/destination-kit/types' +import { InputField, DependsOnConditions } from '@segment/actions-core/destination-kit/types' -export const custom_data_field: InputField = { - label: 'Custom Data', - description: 'Object containing customer information data.', +export const getCustomDataField = (dependsOn: DependsOnConditions): InputField => ({ + label: '[Legacy] Custom Data', + description: 'Object containing custom event data.', type: 'object', + depends_on: dependsOn, properties: { currency: { label: 'Currency', @@ -39,9 +40,30 @@ export const custom_data_field: InputField = { description: 'The price of the Item' }, quantity: { - label: 'quantity', + label: 'Quantity', type: 'integer', - description: 'The number of items purchased' + description: 'The number of items purchased.' + }, + item_brand: { + label: 'Item Brand', + type: 'string', + description: 'The brand of a product.' + }, + item_brand_id: { + label: 'Item Brand ID', + type: 'string', + description: 'The brand ID of a product. Max 64 characters.', + maximum: 64 + }, + item_category: { + label: 'Item Category', + type: 'string', + description: 'The category of a product.' + }, + item_name: { + label: 'Item Name', + type: 'string', + description: 'The name of a product.' } } }, @@ -63,8 +85,28 @@ export const custom_data_field: InputField = { opt_out_type: { label: 'Opt Out Type', description: - 'opt_out_type is the field where we accept opt outs for your users’ privacy preference. It can handle multiple values with commas separated.', + "Accepts opt outs for your users' privacy preference. Can handle multiple values with commas separated.", + type: 'string' + }, + content_brand: { + label: 'Content Brand', + description: 'The brand of the content associated with the event.', type: 'string' + }, + content_category: { + label: 'Content Category', + description: 'The category of the content associated with the event.', + type: 'string' + }, + content_name: { + label: 'Content Name', + description: 'The name of the page or product associated with the event.', + type: 'string' + }, + predicted_ltv: { + label: 'Predicted LTV', + description: 'Predicted lifetime value of user associated with the event.', + type: 'number' } }, default: { @@ -85,4 +127,141 @@ export const custom_data_field: InputField = { '@path': '$.properties.currency' } } -} +}) + +export const getCustomDataField2 = (dependsOn: DependsOnConditions): InputField => ({ + label: 'Custom Data', + description: 'Object containing custom event data.', + type: 'object', + depends_on: dependsOn, + properties: { + currency: { + label: 'Currency', + type: 'string', + description: 'ISO-4217 currency code. If not provided, it will default to the currency set for the ad account.' + }, + value: { + label: 'Value', + type: 'number', + description: + 'Total value of the event. E.g. if there are multiple items in a checkout event, value should be the total price of all items.' + }, + content_ids: { + label: 'Content IDs', + type: 'string', + multiple: true, + description: 'Product IDs as an array of strings.' + }, + num_items: { + label: 'Number of Items', + type: 'integer', + description: 'Total number of products in the event.' + }, + order_id: { + label: 'Order ID', + type: 'string', + description: 'The order ID.' + }, + search_string: { + label: 'Search String', + type: 'string', + description: 'Search string related to the conversion event.' + }, + opt_out_type: { + label: 'Opt Out Type', + type: 'string', + description: + "The field where Pinterest accepts opt outs for your users' privacy preference. It can handle multiple values with commas separated." + }, + content_brand: { + label: 'Content Brand', + type: 'string', + description: 'The brand of the content associated with the event.' + }, + content_category: { + label: 'Content Category', + type: 'string', + description: 'The category of the content associated with the event.' + }, + content_name: { + label: 'Content Name', + type: 'string', + description: 'The name of the page or product associated with the event.' + }, + predicted_ltv: { + label: 'Predicted LTV', + type: 'number', + description: 'Predicted lifetime value of user associated with the event.' + } + }, + default: { + currency: { '@path': '$.properties.currency' }, + value: { + '@if': { + exists: { '@path': '$.properties.price' }, + then: { '@path': '$.properties.price' }, + else: { '@path': '$.properties.value' } + } + }, + order_id: { '@path': '$.properties.order_id' }, + search_string: { '@path': '$.properties.query' } + } +}) + +export const getContentsField = (dependsOn: DependsOnConditions): InputField => ({ + label: 'Contents', + description: 'A list of objects containing information about products.', + type: 'object', + multiple: true, + depends_on: dependsOn, + properties: { + id: { + label: 'Product ID', + type: 'string', + description: 'The id of the item.' + }, + item_price: { + label: 'Price', + type: 'number', + description: 'The price of the item.' + }, + quantity: { + label: 'Quantity', + type: 'integer', + description: 'The number of items purchased.' + }, + item_brand: { + label: 'Item Brand', + type: 'string', + description: 'The brand of the product.' + }, + item_brand_id: { + label: 'Item Brand ID', + type: 'string', + description: 'The brand ID of the product. Max 64 characters.' + }, + item_category: { + label: 'Item Category', + type: 'string', + description: 'The category of the product.' + }, + item_name: { + label: 'Item Name', + type: 'string', + description: 'The name of the product.' + } + }, + default: { + '@arrayPath': [ + '$.properties.products', + { + id: { '@path': '$.product_id' }, + item_price: { '@path': '$.price' }, + quantity: { '@path': '$.quantity' }, + item_brand: { '@path': '$.brand' }, + item_category: { '@path': '$.category' }, + item_name: { '@path': '$.name' } + } + ] + } +}) diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/__snapshots__/index.test.ts.snap b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/__snapshots__/index.test.ts.snap deleted file mode 100644 index 4830f19ea8d..00000000000 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,157 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PinterestConversionApi ReportConversionEvent Should send an event to pinterest successfully,if user data have either of email,hashed_maids or both client_ip_address and client_user_agent 1`] = ` -Object { - "data": Array [ - Object { - "action_source": "web", - "app_id": undefined, - "app_name": "InitechGlobal", - "app_version": "545", - "custom_data": Object { - "content_ids": undefined, - "contents": undefined, - "currency": undefined, - "num_items": 2, - "opt_out_type": undefined, - "order_id": undefined, - "search_string": undefined, - "value": "2000", - }, - "device_brand": undefined, - "device_carrier": undefined, - "device_model": "iPhone7,2", - "device_type": "ios", - "event_id": "test-message-rocnz07d5e8", - "event_name": "checkout", - "event_source_url": "https://segment.com/academy/", - "event_time": 1678694183, - "language": undefined, - "opt_out": true, - "os_version": "8.1.3", - "partner_name": "ss-segment", - "user_data": Object { - "click_id": "click-id1", - "client_ip_address": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1", - "client_user_agent": "5.5.5.5", - "country": Array [ - "79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621", - ], - "ct": Array [ - "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6", - ], - "db": Array [ - "9e4b15bbd40f2429491316d291927f5153b4f8c28738e6ee6284009ce29d13d6", - ], - "em": Array [ - "87924606b4131a8aceeeae8868531fbb9712aaa07a5d3a756b26ce0f5d6ca674", - ], - "external_id": Array [ - "74a6a35e39c525dcf6fd98ba90e79eb3c4358df1ae204e9489d51e6946485b2b", - ], - "fn": Array [ - "44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d", - ], - "ge": Array [ - "62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a", - ], - "hashed_maids": Array [ - "f4c2178860817a2c25d2cb3185aa25779b0ecaf17c30845926218e17a18a9f89", - ], - "ln": Array [ - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", - ], - "partner_id": "partner-id1", - "ph": Array [ - "15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225", - ], - "st": Array [ - "6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126", - ], - "zp": Array [ - "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", - ], - }, - "wifi": undefined, - }, - ], -} -`; - -exports[`PinterestConversionApi ReportConversionEvent Should send an signup event to pinterest successfully,if user data have either of email,hashed_maids or both client_ip_address and client_user_agent 1`] = ` -Object { - "data": Array [ - Object { - "action_source": "web", - "app_id": undefined, - "app_name": "InitechGlobal", - "app_version": "545", - "custom_data": Object { - "content_ids": undefined, - "contents": undefined, - "currency": undefined, - "num_items": undefined, - "opt_out_type": undefined, - "order_id": undefined, - "search_string": undefined, - "value": undefined, - }, - "device_brand": undefined, - "device_carrier": undefined, - "device_model": "iPhone7,2", - "device_type": "ios", - "event_id": "test-message-rocnz07d5e8", - "event_name": "signup", - "event_source_url": "https://segment.com/academy/", - "event_time": 1678694183, - "language": undefined, - "opt_out": true, - "os_version": "8.1.3", - "partner_name": "ss-segment", - "user_data": Object { - "click_id": "click-id1", - "client_ip_address": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1", - "client_user_agent": "5.5.5.5", - "country": Array [ - "79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621", - ], - "ct": Array [ - "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6", - ], - "db": Array [ - "9e4b15bbd40f2429491316d291927f5153b4f8c28738e6ee6284009ce29d13d6", - ], - "em": Array [ - "87924606b4131a8aceeeae8868531fbb9712aaa07a5d3a756b26ce0f5d6ca674", - ], - "external_id": Array [ - "74a6a35e39c525dcf6fd98ba90e79eb3c4358df1ae204e9489d51e6946485b2b", - ], - "fn": Array [ - "44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d", - ], - "ge": Array [ - "62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a", - ], - "hashed_maids": Array [ - "f4c2178860817a2c25d2cb3185aa25779b0ecaf17c30845926218e17a18a9f89", - ], - "ln": Array [ - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", - ], - "partner_id": "partner-id1", - "ph": Array [ - "15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225", - ], - "st": Array [ - "6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126", - ], - "zp": Array [ - "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", - ], - }, - "wifi": undefined, - }, - ], -} -`; diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/index.test.ts b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/index.test.ts index 1aeb16f7d6d..aeebd257cf1 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/index.test.ts @@ -84,48 +84,6 @@ describe('PinterestConversionApi', () => { ).rejects.toThrowError() }) - it('Should send an event to pinterest successfully,if user data have either of email,hashed_maids or both client_ip_address and client_user_agent', async () => { - nock(`https://api.pinterest.com`) - .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) - .reply(200, {}) - - const responses = await testDestination.testAction('reportConversionEvent', { - event, - settings: authData, - useDefaultMappings: true, - mapping: { - event_name: 'checkout', - user_data: { - first_name: ['Gaurav'], - last_name: ['test'], - external_id: ['test_external_id'], - phone: ['123456789'], - gender: ['male'], - city: ['asd'], - state: ['CA'], - zip: ['123456'], - country: ['US'], - hashed_maids: ['test123123'], - date_of_birth: ['1996-02-01'], - email: ['test@gmail.com'], - client_user_agent: '5.5.5.5', - click_id: 'click-id1', - partner_id: 'partner-id1', - client_ip_address: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' - }, - custom_data: { - num_items: '2', - value: 2000 - } - } - }) - expect(responses.length).toBe(1) - expect(responses[0].status).toBe(200) - expect(JSON.parse(responses[0]?.options?.body as string)?.data?.length).toBe(1) - expect(responses[0].options.json).toMatchSnapshot() - }) - it("Should throw an error when user data doesn't have either of email,hashed_maids or both client_ip_address and client_user_agent", async () => { await expect( testDestination.testAction('reportConversionEvent', { @@ -141,86 +99,506 @@ describe('PinterestConversionApi', () => { ) }) - it('Should send an signup event to pinterest successfully,if user data have either of email,hashed_maids or both client_ip_address and client_user_agent', async () => { - nock(`https://api.pinterest.com`) - .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) - .reply(200, {}) - - const responses = await testDestination.testAction('reportConversionEvent', { - event, - settings: authData, - useDefaultMappings: true, - mapping: { - event_name: 'signup', - user_data: { - first_name: ['Gaurav'], - last_name: ['test'], - external_id: ['test_external_id'], - phone: ['123456789'], - gender: ['male'], - city: ['asd'], - state: ['CA'], - zip: ['123456'], - country: ['US'], - hashed_maids: ['test123123'], - date_of_birth: ['1996-02-01'], - email: ['test@gmail.com'], - client_user_agent: '5.5.5.5', - click_id: 'click-id1', - partner_id: 'partner-id1', - client_ip_address: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + describe('legacy mode', () => { + it('should send event using legacy custom_data and flat app/device fields', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'legacy', + event_name: 'checkout', + user_data: { + first_name: ['Gaurav'], + last_name: ['test'], + external_id: ['test_external_id'], + phone: ['123456789'], + gender: ['male'], + city: ['asd'], + state: ['CA'], + zip: ['123456'], + country: ['US'], + hashed_maids: ['test123123'], + date_of_birth: ['1996-02-01'], + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + click_id: 'click-id1', + partner_id: 'partner-id1', + client_ip_address: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + }, + custom_data: { + num_items: '2', + value: 2000 + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].advertiser_tracking_enabled).toBe(true) + expect(body.data[0].custom_data.value).toBe('2000') + expect(body.data[0].custom_data.num_items).toBe(2) + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].app_name).toBe('InitechGlobal') + expect(body.data[0].app_version).toBe('545') + expect(body.data[0].device_model).toBe('iPhone7,2') + expect(body.data[0].device_type).toBe('ios') + expect(body.data[0].os_version).toBe('8.1.3') + expect(body.data[0].app_info).toBeUndefined() + expect(body.data[0].device_info).toBeUndefined() + }) + + it('should send signup event in legacy mode', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'legacy', + event_name: 'signup', + user_data: { + first_name: ['Gaurav'], + last_name: ['test'], + external_id: ['test_external_id'], + phone: ['123456789'], + gender: ['male'], + city: ['asd'], + state: ['CA'], + zip: ['123456'], + country: ['US'], + hashed_maids: ['test123123'], + date_of_birth: ['1996-02-01'], + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + click_id: 'click-id1', + partner_id: 'partner-id1', + client_ip_address: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + } } - } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].event_name).toBe('signup') + expect(body.data[0].partner_name).toBe('ss-segment') + expect(body.data[0].app_info).toBeUndefined() + expect(body.data[0].device_info).toBeUndefined() + }) + + it('should detect pre-hashed data in legacy mode', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'legacy', + event_name: 'checkout', + user_data: { + first_name: ['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d'], + last_name: ['test'], + external_id: ['test_external_id'], + phone: ['63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b'], + gender: ['male'], + city: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + state: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + zip: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + country: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + hashed_maids: ['test123123'], + date_of_birth: ['1996-02-01'], + email: ['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151'], + client_user_agent: '5.5.5.5', + click_id: 'click-id1', + partner_id: 'partner-id1', + client_ip_address: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + }, + custom_data: { + num_items: '2', + value: 2000 + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data.length).toBe(1) + expect(body.data[0].user_data.em).toEqual(['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151']) + expect(body.data[0].user_data.fn).toEqual(['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d']) + expect(body.data[0].user_data.ph).toEqual(['63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b']) + expect(body.data[0].custom_data.value).toBe('2000') + expect(body.data[0].custom_data.num_items).toBe(2) + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].app_name).toBe('InitechGlobal') + expect(body.data[0].device_model).toBe('iPhone7,2') + expect(body.data[0].app_info).toBeUndefined() + expect(body.data[0].device_info).toBeUndefined() }) - expect(responses.length).toBe(1) - expect(responses[0].status).toBe(200) - expect(JSON.parse(responses[0]?.options?.body as string)?.data?.length).toBe(1) - expect(responses[0].options.json).toMatchSnapshot() }) - it('should be able to detect hashed data when flag is set', async () => { - nock(`https://api.pinterest.com`) - .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) - .reply(200, {}) - - const responses = await testDestination.testAction('reportConversionEvent', { - event, - settings: authData, - useDefaultMappings: true, - mapping: { - event_name: 'checkout', - user_data: { - first_name: ['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d'], - last_name: ['test'], - external_id: ['test_external_id'], - phone: ['63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b'], - gender: ['male'], - city: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], - state: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], - zip: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], - country: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], - hashed_maids: ['test123123'], - date_of_birth: ['1996-02-01'], - email: ['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151'], - client_user_agent: '5.5.5.5', - click_id: 'click-id1', - partner_id: 'partner-id1', - client_ip_address: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' - }, - custom_data: { - num_items: '2', - value: 2000 + describe('undefined data_format (existing subscriptions)', () => { + it('should use legacy behavior when data_format is not set', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + mapping: { + event_name: 'checkout', + action_source: 'web', + event_time: '2023-03-13T07:56:23.846Z', + event_id: 'test-message-rocnz07d5e8', + app_name: 'InitechGlobal', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + }, + custom_data: { + value: 100, + currency: 'USD' + } } - } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].custom_data.value).toBe('100') + expect(body.data[0].custom_data.currency).toBe('USD') + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].app_name).toBe('InitechGlobal') + expect(body.data[0].app_info).toBeUndefined() + expect(body.data[0].device_info).toBeUndefined() + }) + }) + + describe('latest mode', () => { + it('should send event using latest fields with app_info and device_info', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'latest', + event_name: 'checkout', + user_data: { + first_name: ['Gaurav'], + last_name: ['test'], + external_id: ['test_external_id'], + phone: ['123456789'], + gender: ['male'], + city: ['asd'], + state: ['CA'], + zip: ['123456'], + country: ['US'], + hashed_maids: ['test123123'], + date_of_birth: ['1996-02-01'], + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + click_id: 'click-id1', + partner_id: 'partner-id1', + client_ip_address: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + }, + custom_data_2: { + value: 2000, + num_items: 2, + currency: 'USD' + }, + contents: [ + { + id: 'sku_123', + item_price: 74.99, + quantity: 2, + item_brand: 'Brand A', + item_category: 'Shoes', + item_name: 'Running Shoe' + } + ] + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].advertiser_tracking_enabled).toBe(true) + expect(body.data[0].custom_data.value).toBe('2000') + expect(body.data[0].custom_data.num_items).toBe(2) + expect(body.data[0].custom_data.currency).toBe('USD') + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].custom_data.contents).toEqual([ + { + id: 'sku_123', + item_price: '74.99', + quantity: 2, + item_brand: 'Brand A', + item_category: 'Shoes', + item_name: 'Running Shoe' + } + ]) + expect(body.data[0].app_info).toEqual({ + app_name: 'InitechGlobal', + app_package_name: 'com.production.segment', + app_version: '545', + user_agent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + }) + expect(body.data[0].device_info).toEqual({ + brand: 'Apple', + model: 'iPhone7,2', + type: 'ios', + os_version: '8.1.3', + timezone: 'Europe/Amsterdam' + }) + expect(body.data[0].app_id).toBeUndefined() + expect(body.data[0].app_name).toBeUndefined() + expect(body.data[0].device_brand).toBeUndefined() + expect(body.data[0].device_model).toBeUndefined() + }) + + it('should detect pre-hashed data in latest mode', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'latest', + event_name: 'checkout', + user_data: { + first_name: ['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d'], + last_name: ['test'], + external_id: ['test_external_id'], + phone: ['63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b'], + gender: ['male'], + city: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + state: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + zip: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + country: ['92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d'], + hashed_maids: ['test123123'], + date_of_birth: ['1996-02-01'], + email: ['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151'], + client_user_agent: '5.5.5.5', + click_id: 'click-id1', + partner_id: 'partner-id1', + client_ip_address: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + }, + custom_data_2: { + value: 2000, + num_items: 2 + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].user_data.em).toEqual(['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151']) + expect(body.data[0].user_data.fn).toEqual(['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d']) + expect(body.data[0].user_data.ph).toEqual(['63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b']) + expect(body.data[0].custom_data.value).toBe('2000') + expect(body.data[0].custom_data.num_items).toBe(2) + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].app_info.app_name).toBe('InitechGlobal') + expect(body.data[0].device_info.model).toBe('iPhone7,2') + }) + }) + + describe('install_time conversion', () => { + it('should convert install_time ISO timestamp to unix seconds', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'latest', + event_name: 'app_install', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + }, + app_info: { + app_id: '429047995', + app_name: 'MyApp', + install_time: '2025-02-10T18:17:49.000Z' + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].app_info.install_time).toBe(1739211469) + expect(body.data[0].app_info.app_id).toBe('429047995') + expect(body.data[0].app_info.app_name).toBe('MyApp') + }) + }) + + describe('predicted_ltv conversion', () => { + it('should convert predicted_ltv number to string', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'latest', + event_name: 'checkout', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + }, + custom_data_2: { + value: 149.99, + predicted_ltv: 2794.82 + } + } + }) + expect(responses.length).toBe(1) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].custom_data.predicted_ltv).toBe('2794.82') + expect(body.data[0].custom_data.value).toBe('149.99') + }) + }) + + describe('empty app_info and device_info omission', () => { + it('should omit app_info when all fields are empty', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + mapping: { + data_format: 'latest', + event_name: 'page_visit', + event_time: '2023-03-13T07:56:23.846Z', + event_id: 'test-123', + action_source: 'web', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + }, + app_info: {}, + device_info: {} + } + }) + expect(responses.length).toBe(1) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].app_info).toBeUndefined() + expect(body.data[0].device_info).toBeUndefined() + }) + }) + + describe('new event names', () => { + it('should accept start_trial as a valid event name', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + useDefaultMappings: true, + mapping: { + data_format: 'latest', + event_name: 'start_trial', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].event_name).toBe('start_trial') + }) + }) + + describe('conditional required field', () => { + it('should not require app_name in latest mode', async () => { + nock(`https://api.pinterest.com`) + .post(`/${API_VERSION}/ad_accounts/${authData.ad_account_id}/events`) + .reply(200, {}) + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + mapping: { + data_format: 'latest', + event_name: 'checkout', + action_source: 'web', + event_time: '2023-03-13T07:56:23.846Z', + event_id: 'test-123', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + } + } + }) + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) + + it('should require app_name in legacy mode', async () => { + await expect( + testDestination.testAction('reportConversionEvent', { + event, + settings: authData, + mapping: { + data_format: 'legacy', + event_name: 'checkout', + action_source: 'web', + event_time: '2023-03-13T07:56:23.846Z', + event_id: 'test-123', + user_data: { + email: ['test@gmail.com'], + client_user_agent: '5.5.5.5', + client_ip_address: '1.2.3.4' + } + } + }) + ).rejects.toThrowError() }) - expect(responses.length).toBe(1) - expect(responses[0].status).toBe(200) - expect(JSON.parse(responses[0]?.options?.body as string)?.data?.length).toBe(1) - expect(responses[0].options.body).toBe( - '{"data":[{"event_name":"checkout","action_source":"web","event_time":1678694183,"event_id":"test-message-rocnz07d5e8","event_source_url":"https://segment.com/academy/","partner_name":"ss-segment","opt_out":true,"user_data":{"em":["c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151"],"ph":["63af7d494c194a90e1cf1db5371c13f97db650161aa803e67182c0dbaf668c7b"],"ge":["62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a"],"db":["9e4b15bbd40f2429491316d291927f5153b4f8c28738e6ee6284009ce29d13d6"],"ln":["9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"],"fn":["44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d"],"ct":["92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d"],"st":["92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d"],"zp":["92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d"],"country":["92db9c574d420b2437b29d898d55604f61df6c17f5163e53337f2169dd70d38d"],"external_id":["74a6a35e39c525dcf6fd98ba90e79eb3c4358df1ae204e9489d51e6946485b2b"],"client_ip_address":"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1","client_user_agent":"5.5.5.5","hashed_maids":["f4c2178860817a2c25d2cb3185aa25779b0ecaf17c30845926218e17a18a9f89"],"click_id":"click-id1","partner_id":"partner-id1"},"custom_data":{"value":"2000","num_items":2},"app_name":"InitechGlobal","app_version":"545","device_model":"iPhone7,2","device_type":"ios","os_version":"8.1.3"}]}' - ) }) }) }) diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/generated-types.ts b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/generated-types.ts index cd9ad9708d8..a9bcc24d674 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/generated-types.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/generated-types.ts @@ -1,6 +1,10 @@ // Generated file. DO NOT MODIFY IT BY HAND. export interface Payload { + /** + * Switch between the latest field configuration and the legacy fields. New instances default to the latest fields. + */ + data_format?: string /** * The conversion event type. For custom events, you must use the predefined event name "custom". Please refer to the possible event types in [Pinterest API docs](https://developers.pinterest.com/docs/api/v5/#operation/events/create). */ @@ -25,6 +29,10 @@ export interface Payload { * When action_source is web or offline, it defines whether the user has opted out of tracking for web conversion events. While when action_source is app_android or app_ios, it defines whether the user has enabled Limit Ad Tracking on their iOS device, or opted out of Ads Personalization on their Android device. */ opt_out?: boolean + /** + * Defines whether the user has enabled ATT permission on their iOS device. + */ + advertiser_tracking_enabled?: boolean /** * Object containing customer information data. Note, It is required at least one of 1) em, 2) hashed_maids or 3) pair client_ip_address + client_user_agent.. */ @@ -95,7 +103,7 @@ export interface Payload { partner_id?: string | null } /** - * Object containing customer information data. + * Object containing custom event data. */ custom_data?: { /** @@ -123,9 +131,25 @@ export interface Payload { */ item_price?: number /** - * The number of items purchased + * The number of items purchased. */ quantity?: number + /** + * The brand of a product. + */ + item_brand?: string + /** + * The brand ID of a product. Max 64 characters. + */ + item_brand_id?: string + /** + * The category of a product. + */ + item_category?: string + /** + * The name of a product. + */ + item_name?: string }[] /** * Total number of products in the event. @@ -140,9 +164,25 @@ export interface Payload { */ search_string?: string /** - * opt_out_type is the field where we accept opt outs for your users’ privacy preference. It can handle multiple values with commas separated. + * Accepts opt outs for your users' privacy preference. Can handle multiple values with commas separated. */ opt_out_type?: string + /** + * The brand of the content associated with the event. + */ + content_brand?: string + /** + * The category of the content associated with the event. + */ + content_category?: string + /** + * The name of the page or product associated with the event. + */ + content_name?: string + /** + * Predicted lifetime value of user associated with the event. + */ + predicted_ltv?: number } /** * The app store app ID. @@ -151,7 +191,7 @@ export interface Payload { /** * Name of the app. */ - app_name: string + app_name?: string /** * Version of the app. */ @@ -161,7 +201,7 @@ export interface Payload { */ device_brand?: string /** - * User device’s mobile carrier. + * User device's mobile carrier. */ device_carrier?: string /** @@ -176,6 +216,230 @@ export interface Payload { * Version of the device operating system. */ os_version?: string + /** + * Object containing custom event data. + */ + custom_data_2?: { + /** + * ISO-4217 currency code. If not provided, it will default to the currency set for the ad account. + */ + currency?: string + /** + * Total value of the event. E.g. if there are multiple items in a checkout event, value should be the total price of all items. + */ + value?: number + /** + * Product IDs as an array of strings. + */ + content_ids?: string[] + /** + * Total number of products in the event. + */ + num_items?: number + /** + * The order ID. + */ + order_id?: string + /** + * Search string related to the conversion event. + */ + search_string?: string + /** + * The field where Pinterest accepts opt outs for your users' privacy preference. It can handle multiple values with commas separated. + */ + opt_out_type?: string + /** + * The brand of the content associated with the event. + */ + content_brand?: string + /** + * The category of the content associated with the event. + */ + content_category?: string + /** + * The name of the page or product associated with the event. + */ + content_name?: string + /** + * Predicted lifetime value of user associated with the event. + */ + predicted_ltv?: number + } + /** + * A list of objects containing information about products. + */ + contents?: { + /** + * The id of the item. + */ + id?: string + /** + * The price of the item. + */ + item_price?: number + /** + * The number of items purchased. + */ + quantity?: number + /** + * The brand of the product. + */ + item_brand?: string + /** + * The brand ID of the product. Max 64 characters. + */ + item_brand_id?: string + /** + * The category of the product. + */ + item_category?: string + /** + * The name of the product. + */ + item_name?: string + }[] + /** + * Object containing information about the application where event occurred. + */ + app_info?: { + /** + * App ID in Google Play Store, AppStore or other stores. + */ + app_id?: string + /** + * Name of the app. + */ + app_name?: string + /** + * App package name. + */ + app_package_name?: string + /** + * The name of the app distributor or store from which the app was installed. + */ + app_store?: string + /** + * App version. + */ + app_version?: string + /** + * App install time. Will be converted to Unix timestamp in seconds before sending. + */ + install_time?: string | number + /** + * User Agent request header. + */ + user_agent?: string + /** + * Inner height of the window or viewport. + */ + window_height?: number + /** + * Inner width of the window or viewport. + */ + window_width?: number + } + /** + * Object containing information about the device where event occurred. + */ + device_info?: { + /** + * Battery charge level percentage. + */ + battery_level?: number + /** + * Device brand. + */ + brand?: string + /** + * User device's mobile carrier. + */ + carrier?: string + /** + * Number of CPU cores. + */ + cpu_cores?: number + /** + * External storage free space in GB. + */ + external_storage_free_space?: number + /** + * External storage size in GB. + */ + external_storage_size?: number + /** + * Device form factor (desktop, laptop, cellphone, tablet, smartwatch, tv, vr, console, other). + */ + form_factor?: string + /** + * Kernel version of the device's operating system. + */ + kernel_version?: string + /** + * List of user installed languages. ISO 639-1 format. + */ + languages?: string[] + /** + * Device locale in BCP-47 format. + */ + locale?: string + /** + * Device model name. + */ + model?: string + /** + * Network type (wifi, cellular_2g, cellular_3g, cellular_4g, cellular_5g, cellular_6g, ethernet, unknown). + */ + network_type?: string + /** + * OS Family (ios, android, macos, windows, linux, bsd, other). + */ + os_family?: string + /** + * Short name of the OS. + */ + os_name?: string + /** + * Marketing name for the release version. + */ + os_release_name?: string + /** + * Full name of the OS version. + */ + os_version?: string + /** + * Screen density, PPI. + */ + screen_density?: number + /** + * Screen height in pixels. + */ + screen_height?: number + /** + * Screen width in pixels. + */ + screen_width?: number + /** + * Internal storage free space in GB. + */ + storage_free_space?: number + /** + * Internal storage size in GB. + */ + storage_size?: number + /** + * Device timezone. + */ + timezone?: string + /** + * Timezone abbreviation. + */ + timezone_abbr?: string + /** + * Device type. + */ + type?: string + } /** * Whether the event occurred when the user device was connected to wifi. */ diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts index aee1acd0f89..774d1de2051 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -1,18 +1,43 @@ import type { ActionDefinition, RequestClient } from '@segment/actions-core' import { IntegrationError } from '@segment/actions-core' -import { API_VERSION, PARTNER_NAME } from '../constants' +import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' +import { API_VERSION, PARTNER_NAME, EVENT_NAME } from '../constants' import type { Settings } from '../generated-types' -import { custom_data_field } from '../pinterest-capi-custom-data' +import { getCustomDataField, getCustomDataField2, getContentsField } from '../pinterest-capi-custom-data' import { user_data_field, hash_user_data } from '../pinterset-capi-user-data' import type { Payload } from './generated-types' +import type { PinterestEventPayload, LegacyPinterestEventPayload, CustomData } from '../types' import isEmpty from 'lodash/isEmpty' import dayjs from '../../../lib/dayjs' +const DEPENDS_ON_LEGACY: DependsOnConditions = { + match: 'any', + conditions: [ + { fieldKey: 'data_format', operator: 'is', value: 'legacy' }, + { fieldKey: 'data_format', operator: 'is', value: undefined } + ] +} + +const DEPENDS_ON_LATEST: DependsOnConditions = { + conditions: [{ fieldKey: 'data_format', operator: 'is', value: 'latest' }] +} + const action: ActionDefinition = { title: 'Report Conversion Event', description: 'Report events directly to Pinterest. Data shared can power Pinterest solutions that will help evaluate ads effectiveness and improve content, targeting, and placement of future ads.', fields: { + data_format: { + label: 'Use Latest Fields', + description: + 'Switch between the latest field configuration and the legacy fields. New instances default to the latest fields.', + type: 'string', + choices: [ + { label: 'Latest Fields', value: 'latest' }, + { label: 'Legacy Fields', value: 'legacy' } + ], + default: 'latest' + }, event_name: { label: 'Event Name', description: @@ -20,15 +45,28 @@ const action: ActionDefinition = { type: 'string', required: true, choices: [ - { label: 'Add to Cart', value: 'add_to_cart' }, - { label: 'Checkout', value: 'checkout' }, - { label: 'Lead', value: 'lead' }, - { label: 'Page Visit', value: 'page_visit' }, - { label: 'Search', value: 'search' }, - { label: 'Sign Up', value: 'signup' }, - { label: 'View Category', value: 'view_category' }, - { label: 'Watch Video', value: 'watch_video' }, - { label: 'Custom', value: 'custom' } + { label: 'Add Payment Info', value: EVENT_NAME.ADD_PAYMENT_INFO }, + { label: 'Add to Cart', value: EVENT_NAME.ADD_TO_CART }, + { label: 'Add to Wishlist', value: EVENT_NAME.ADD_TO_WISHLIST }, + { label: 'App Install', value: EVENT_NAME.APP_INSTALL }, + { label: 'App Open', value: EVENT_NAME.APP_OPEN }, + { label: 'Checkout', value: EVENT_NAME.CHECKOUT }, + { label: 'Contact', value: EVENT_NAME.CONTACT }, + { label: 'Custom', value: EVENT_NAME.CUSTOM }, + { label: 'Customize Product', value: EVENT_NAME.CUSTOMIZE_PRODUCT }, + { label: 'Find Location', value: EVENT_NAME.FIND_LOCATION }, + { label: 'Initiate Checkout', value: EVENT_NAME.INITIATE_CHECKOUT }, + { label: 'Lead', value: EVENT_NAME.LEAD }, + { label: 'Page Visit', value: EVENT_NAME.PAGE_VISIT }, + { label: 'Schedule', value: EVENT_NAME.SCHEDULE }, + { label: 'Search', value: EVENT_NAME.SEARCH }, + { label: 'Sign Up', value: EVENT_NAME.SIGNUP }, + { label: 'Start Trial', value: EVENT_NAME.START_TRIAL }, + { label: 'Submit Application', value: EVENT_NAME.SUBMIT_APPLICATION }, + { label: 'Subscribe', value: EVENT_NAME.SUBSCRIBE }, + { label: 'View Category', value: EVENT_NAME.VIEW_CATEGORY }, + { label: 'View Content', value: EVENT_NAME.VIEW_CONTENT }, + { label: 'Watch Video', value: EVENT_NAME.WATCH_VIDEO } ] }, action_source: { @@ -80,73 +118,328 @@ const action: ActionDefinition = { type: 'boolean', default: true }, + advertiser_tracking_enabled: { + label: 'Advertiser Tracking Enabled', + description: 'Defines whether the user has enabled ATT permission on their iOS device.', + type: 'boolean', + default: { + '@path': '$.context.device.adTrackingEnabled' + } + }, user_data: user_data_field, - custom_data: custom_data_field, + + // --- Legacy fields (shown when data_format is 'legacy' or undefined) --- + custom_data: getCustomDataField(DEPENDS_ON_LEGACY), app_id: { - label: 'App ID', + label: '[Legacy] App ID', description: 'The app store app ID.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': 'context.app.id' } }, app_name: { - label: 'App Name', - description: 'Name of the app. ', + label: '[Legacy] App Name', + description: 'Name of the app.', type: 'string', - required: true, + required: DEPENDS_ON_LEGACY, + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.app.name' } }, app_version: { - label: 'App Version', + label: '[Legacy] App Version', description: 'Version of the app.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.app.version' } }, device_brand: { - label: 'Device Brand', + label: '[Legacy] Device Brand', description: 'Brand of the user device.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.device.brand' } }, device_carrier: { - label: 'Device Carrier', - description: 'User device’s mobile carrier. ', + label: '[Legacy] Device Carrier', + description: "User device's mobile carrier.", type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': 'context.device.carrier' } }, device_model: { - label: 'Device Model', + label: '[Legacy] Device Model', description: 'Model of the user device.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.device.model' } }, device_type: { - label: 'Device Type', + label: '[Legacy] Device Type', description: 'Type of the user device.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.device.type' } }, os_version: { - label: 'OS Version', + label: '[Legacy] OS Version', description: 'Version of the device operating system.', type: 'string', + depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.os.version' } }, + + // --- Latest fields (shown when data_format is 'latest') --- + custom_data_2: getCustomDataField2(DEPENDS_ON_LATEST), + contents: getContentsField(DEPENDS_ON_LATEST), + app_info: { + label: 'App Info', + description: 'Object containing information about the application where event occurred.', + type: 'object', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + depends_on: DEPENDS_ON_LATEST, + properties: { + app_id: { + label: 'App ID', + type: 'string', + description: 'App ID in Google Play Store, AppStore or other stores.' + }, + app_name: { + label: 'App Name', + type: 'string', + description: 'Name of the app.' + }, + app_package_name: { + label: 'App Package Name', + type: 'string', + description: 'App package name.' + }, + app_store: { + label: 'App Store', + type: 'string', + description: 'The name of the app distributor or store from which the app was installed.' + }, + app_version: { + label: 'App Version', + type: 'string', + description: 'App version.' + }, + install_time: { + label: 'Install Time', + type: 'datetime', + description: 'App install time. Accepts ISO 8601 format or Unix timestamp in seconds (10 digit).' + }, + user_agent: { + label: 'User Agent', + type: 'string', + description: 'User Agent request header.' + }, + window_height: { + label: 'Window Height', + type: 'integer', + description: 'Inner height of the window or viewport.' + }, + window_width: { + label: 'Window Width', + type: 'integer', + description: 'Inner width of the window or viewport.' + } + }, + default: { + app_name: { '@path': '$.context.app.name' }, + app_package_name: { '@path': '$.context.app.namespace' }, + app_version: { '@path': '$.context.app.version' }, + user_agent: { '@path': '$.context.userAgent' }, + window_height: { '@path': '$.context.screen.height' }, + window_width: { '@path': '$.context.screen.width' } + } + }, + device_info: { + label: 'Device Info', + description: 'Object containing information about the device where event occurred.', + type: 'object', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + depends_on: DEPENDS_ON_LATEST, + properties: { + battery_level: { + label: 'Battery Level', + type: 'integer', + description: 'Battery charge level percentage.' + }, + brand: { + label: 'Brand', + type: 'string', + description: 'Device brand.' + }, + carrier: { + label: 'Carrier', + type: 'string', + description: "User device's mobile carrier." + }, + cpu_cores: { + label: 'CPU Cores', + type: 'integer', + description: 'Number of CPU cores.' + }, + external_storage_free_space: { + label: 'External Storage Free Space', + type: 'integer', + description: 'External storage free space in GB.' + }, + external_storage_size: { + label: 'External Storage Size', + type: 'integer', + description: 'External storage size in GB.' + }, + form_factor: { + label: 'Form Factor', + type: 'string', + description: 'Device form factor (desktop, laptop, cellphone, tablet, smartwatch, tv, vr, console, other).', + choices: [ + { label: 'desktop', value: 'desktop' }, + { label: 'laptop', value: 'laptop' }, + { label: 'cellphone', value: 'cellphone' }, + { label: 'tablet', value: 'tablet' }, + { label: 'smartwatch', value: 'smartwatch' }, + { label: 'tv', value: 'tv' }, + { label: 'vr', value: 'vr' }, + { label: 'console', value: 'console' }, + { label: 'other', value: 'other' } + ], + default: 'other' + }, + kernel_version: { + label: 'Kernel Version', + type: 'string', + description: "Kernel version of the device's operating system." + }, + languages: { + label: 'Languages', + type: 'string', + multiple: true, + description: 'List of user installed languages. ISO 639-1 format. For example, ["en", "fr", "es"].' + }, + locale: { + label: 'Locale', + type: 'string', + description: 'Device locale in BCP-47 format.' + }, + model: { + label: 'Model', + type: 'string', + description: 'Device model name.' + }, + network_type: { + label: 'Network Type', + type: 'string', + description: + 'Network type (wifi, cellular_2g, cellular_3g, cellular_4g, cellular_5g, cellular_6g, ethernet, unknown).' + }, + os_family: { + label: 'OS Family', + type: 'string', + description: 'OS Family (ios, android, macos, windows, linux, bsd, other).', + choices: [ + { label: 'ios', value: 'ios' }, + { label: 'android', value: 'android' }, + { label: 'macos', value: 'macos' }, + { label: 'windows', value: 'windows' }, + { label: 'linux', value: 'linux' }, + { label: 'bsd', value: 'bsd' }, + { label: 'other', value: 'other' } + ], + default: 'other' + }, + os_name: { + label: 'OS Name', + type: 'string', + description: 'Short name of the OS.', + maximum: 100 + }, + os_release_name: { + label: 'OS Release Name', + type: 'string', + description: 'Marketing name for the release version.' + }, + os_version: { + label: 'OS Version', + type: 'string', + description: 'Full name of the OS version.', + maximum: 100 + }, + screen_density: { + label: 'Screen Density', + type: 'integer', + description: 'Screen density, PPI.' + }, + screen_height: { + label: 'Screen Height', + type: 'integer', + description: 'Screen height in pixels.' + }, + screen_width: { + label: 'Screen Width', + type: 'integer', + description: 'Screen width in pixels.' + }, + storage_free_space: { + label: 'Storage Free Space', + type: 'integer', + description: 'Internal storage free space in GB.' + }, + storage_size: { + label: 'Storage Size', + type: 'integer', + description: 'Internal storage size in GB.' + }, + timezone: { + label: 'Timezone', + type: 'string', + description: 'Device timezone.' + }, + timezone_abbr: { + label: 'Timezone Abbreviation', + type: 'string', + description: 'Timezone abbreviation.' + }, + type: { + label: 'Type', + type: 'string', + description: 'Device type.' + } + }, + default: { + brand: { '@path': '$.context.device.manufacturer' }, + carrier: { '@path': '$.context.network.carrier' }, + model: { '@path': '$.context.device.model' }, + type: { '@path': '$.context.device.type' }, + os_version: { '@path': '$.context.os.version' }, + locale: { '@path': '$.context.locale' }, + screen_density: { '@path': '$.context.screen.density' }, + screen_height: { '@path': '$.context.screen.height' }, + screen_width: { '@path': '$.context.screen.width' }, + timezone: { '@path': '$.context.timezone' } + } + }, + + // --- Shared fields (always shown) --- wifi: { label: 'Wifi', description: 'Whether the event occurred when the user device was connected to wifi.', @@ -165,6 +458,7 @@ const action: ActionDefinition = { return processPayload(request, settings, payload) } } + async function processPayload(request: RequestClient, settings: Settings, payload: Payload) { if ( isEmpty(payload.user_data?.email) && @@ -187,7 +481,80 @@ async function processPayload(request: RequestClient, settings: Settings, payloa }) } -function createPinterestPayload(payload: Payload) { +function convertInstallTime(value: string | number | undefined | null): number | undefined { + if (value === undefined || value === null || value === '') return undefined + if (typeof value === 'number') return value + const parsed = dayjs.utc(value) + if (!parsed.isValid()) return undefined + return parsed.unix() +} + +function buildAppInfo(payload: Payload) { + const appInfo = { + ...payload.app_info, + install_time: convertInstallTime(payload.app_info?.install_time) + } + const hasContent = Object.values(appInfo).some((v) => v !== undefined && v !== null) + return hasContent ? appInfo : undefined +} + +function buildDeviceInfo(payload: Payload) { + if (!payload.device_info) return undefined + const hasContent = Object.values(payload.device_info).some((v) => v !== undefined && v !== null) + return hasContent ? payload.device_info : undefined +} + +function buildCustomData(payload: Payload): CustomData { + const isStructured = payload.data_format === 'latest' + + if (isStructured) { + return { + currency: payload.custom_data_2?.currency, + value: typeof payload.custom_data_2?.value === 'number' ? String(payload.custom_data_2.value) : undefined, + content_ids: payload.custom_data_2?.content_ids, + contents: payload.contents?.map((item) => ({ + ...item, + item_price: typeof item.item_price === 'number' ? String(item.item_price) : undefined + })), + num_items: payload.custom_data_2?.num_items, + order_id: payload.custom_data_2?.order_id, + search_string: payload.custom_data_2?.search_string, + opt_out_type: payload.custom_data_2?.opt_out_type, + content_brand: payload.custom_data_2?.content_brand, + content_category: payload.custom_data_2?.content_category, + content_name: payload.custom_data_2?.content_name, + predicted_ltv: + typeof payload.custom_data_2?.predicted_ltv === 'number' + ? String(payload.custom_data_2.predicted_ltv) + : undefined, + np: PARTNER_NAME + } + } + + return { + currency: payload.custom_data?.currency, + value: typeof payload.custom_data?.value === 'number' ? String(payload.custom_data.value) : undefined, + content_ids: payload.custom_data?.content_ids, + contents: payload.custom_data?.contents?.map((item) => ({ + ...item, + item_price: typeof item.item_price === 'number' ? String(item.item_price) : undefined + })), + num_items: payload.custom_data?.num_items, + order_id: payload.custom_data?.order_id, + search_string: payload.custom_data?.search_string, + opt_out_type: payload.custom_data?.opt_out_type, + content_brand: payload.custom_data?.content_brand, + content_category: payload.custom_data?.content_category, + content_name: payload.custom_data?.content_name, + predicted_ltv: + typeof payload.custom_data?.predicted_ltv === 'number' ? String(payload.custom_data.predicted_ltv) : undefined, + np: PARTNER_NAME + } +} + +function createPinterestPayload(payload: Payload): (PinterestEventPayload | LegacyPinterestEventPayload)[] { + const isStructured = payload.data_format === 'latest' + return [ { event_name: payload.event_name, @@ -197,28 +564,19 @@ function createPinterestPayload(payload: Payload) { event_source_url: payload.event_source_url, partner_name: PARTNER_NAME, opt_out: payload.opt_out, + advertiser_tracking_enabled: payload.advertiser_tracking_enabled, user_data: hash_user_data({ user_data: payload.user_data }), - custom_data: { - currency: payload?.custom_data?.currency, - value: typeof payload?.custom_data?.value === 'number' ? String(payload.custom_data.value) : undefined, - content_ids: payload.custom_data?.content_ids, - contents: payload.custom_data?.contents?.map((item) => ({ - ...item, - item_price: typeof item.item_price === 'number' ? String(item.item_price) : undefined - })), - num_items: payload.custom_data?.num_items, - order_id: payload.custom_data?.order_id, - search_string: payload.custom_data?.search_string, - opt_out_type: payload.custom_data?.opt_out_type - }, - app_id: payload.app_id, - app_name: payload.app_name, - app_version: payload.app_version, - device_brand: payload.device_brand, - device_carrier: payload.device_carrier, - device_model: payload.device_model, - device_type: payload.device_type, - os_version: payload.os_version, + custom_data: buildCustomData(payload), + app_id: isStructured ? undefined : payload.app_id, + app_name: isStructured ? undefined : payload.app_name, + app_version: isStructured ? undefined : payload.app_version, + app_info: isStructured ? buildAppInfo(payload) : undefined, + device_brand: isStructured ? undefined : payload.device_brand, + device_carrier: isStructured ? undefined : payload.device_carrier, + device_model: isStructured ? undefined : payload.device_model, + device_type: isStructured ? undefined : payload.device_type, + os_version: isStructured ? undefined : payload.os_version, + device_info: isStructured ? buildDeviceInfo(payload) : undefined, wifi: payload.wifi, language: payload.language } diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/types.ts b/packages/destination-actions/src/destinations/pinterest-conversions/types.ts index f2087b125b7..5f5e35440e1 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/types.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/types.ts @@ -7,3 +7,132 @@ export class PinterestConversionsTestAuthenticationError extends HTTPError { } } } + +// --- Pinterest Conversions API Event Types --- + +export interface UserData { + em?: string[] + ph?: string[] + ge?: string[] + db?: string[] + ln?: string[] + fn?: string[] + ct?: string[] + st?: string[] + zp?: string[] + country?: string[] + external_id?: string[] + client_ip_address?: string + client_user_agent?: string + hashed_maids?: string[] + click_id?: string | null + partner_id?: string | null +} + +export interface ContentsItem { + id?: string + // Converted from number to string before sending + item_price?: string + quantity?: number + item_brand?: string + item_brand_id?: string + item_category?: string + item_name?: string +} + +export interface CustomData { + currency?: string + // Converted from number to string before sending + value?: string + content_ids?: string[] + contents?: ContentsItem[] + num_items?: number + order_id?: string + search_string?: string + opt_out_type?: string + content_brand?: string + content_category?: string + content_name?: string + // Converted from number to string before sending + predicted_ltv?: string + np?: string +} + +export interface AppInfo { + app_id?: string + app_name?: string + app_package_name?: string + app_store?: string + app_version?: string + install_time?: number + user_agent?: string + window_height?: number + window_width?: number +} + +export interface DeviceInfo { + battery_level?: number + brand?: string + carrier?: string + cpu_cores?: number + external_storage_free_space?: number + external_storage_size?: number + form_factor?: string + kernel_version?: string + languages?: string[] + locale?: string + model?: string + network_type?: string + os_family?: string + os_name?: string + os_release_name?: string + os_version?: string + screen_density?: number + screen_height?: number + screen_width?: number + storage_free_space?: number + storage_size?: number + timezone?: string + timezone_abbr?: string + type?: string +} + +export interface PinterestEventPayload { + event_name: string + action_source: string + event_time: number + event_id: string + event_source_url?: string + partner_name: string + opt_out?: boolean + advertiser_tracking_enabled?: boolean + user_data: UserData + custom_data: CustomData + app_info?: AppInfo + device_info?: DeviceInfo + wifi?: boolean + language?: string +} + +export interface LegacyPinterestEventPayload { + event_name: string + action_source: string + event_time: number + event_id: string + event_source_url?: string + partner_name: string + opt_out?: boolean + advertiser_tracking_enabled?: boolean + user_data: UserData + custom_data: CustomData + app_id?: string + app_name?: string + app_version?: string + device_brand?: string + device_carrier?: string + device_model?: string + device_type?: string + os_version?: string + wifi?: boolean + language?: string +}