From 369897782f6efc17e760efe0fec3fb8c515f7dd1 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 11:32:20 +0100 Subject: [PATCH 01/16] STRATCONN-6780 - [Pinterest] - new events --- .../pinterest-conversions/constants.ts | 15 +- .../pinterest-capi-custom-data.ts | 42 ++- .../__snapshots__/index.test.ts.snap | 38 +++ .../__tests__/index.test.ts | 12 +- .../reportConversionEvent/generated-types.ts | 182 ++++++++++++- .../reportConversionEvent/index.ts | 240 +++++++++++++++++- 6 files changed, 518 insertions(+), 11 deletions(-) diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts b/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts index 618730fa59d..cdf414fa89c 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/constants.ts @@ -2,14 +2,27 @@ 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', + VIEW_CONTENT: 'view_content', WATCH_VIDEO: 'watch_video' } export const ACTION_SOURCE = ['app_android', 'app_ios', 'web', 'offline'] 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..6631429ce20 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 @@ -42,6 +42,26 @@ export const custom_data_field: InputField = { label: 'quantity', type: 'integer', 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.' + }, + 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 +83,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.', + "opt_out_type is the field where we accept opt outs for your users' privacy preference. It 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: { 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 index 4830f19ea8d..b625dfb7d90 100644 --- 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 @@ -5,21 +5,40 @@ Object { "data": Array [ Object { "action_source": "web", + "advertiser_tracking_enabled": true, "app_id": undefined, + "app_info": Object { + "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", + }, "app_name": "InitechGlobal", "app_version": "545", "custom_data": Object { + "content_brand": undefined, + "content_category": undefined, "content_ids": undefined, + "content_name": undefined, "contents": undefined, "currency": undefined, + "np": "ss-segment", "num_items": 2, "opt_out_type": undefined, "order_id": undefined, + "predicted_ltv": undefined, "search_string": undefined, "value": "2000", }, "device_brand": undefined, "device_carrier": undefined, + "device_info": Object { + "model": "iPhone7,2", + "os_family": "iPhone OS", + "os_version": "8.1.3", + "timezone": "Europe/Amsterdam", + "type": "ios", + }, "device_model": "iPhone7,2", "device_type": "ios", "event_id": "test-message-rocnz07d5e8", @@ -83,21 +102,40 @@ Object { "data": Array [ Object { "action_source": "web", + "advertiser_tracking_enabled": true, "app_id": undefined, + "app_info": Object { + "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", + }, "app_name": "InitechGlobal", "app_version": "545", "custom_data": Object { + "content_brand": undefined, + "content_category": undefined, "content_ids": undefined, + "content_name": undefined, "contents": undefined, "currency": undefined, + "np": "ss-segment", "num_items": undefined, "opt_out_type": undefined, "order_id": undefined, + "predicted_ltv": undefined, "search_string": undefined, "value": undefined, }, "device_brand": undefined, "device_carrier": undefined, + "device_info": Object { + "model": "iPhone7,2", + "os_family": "iPhone OS", + "os_version": "8.1.3", + "timezone": "Europe/Amsterdam", + "type": "ios", + }, "device_model": "iPhone7,2", "device_type": "ios", "event_id": "test-message-rocnz07d5e8", 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..87c7effbcff 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 @@ -218,9 +218,15 @@ describe('PinterestConversionApi', () => { 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"}]}' - ) + const body = JSON.parse(responses[0].options.body as string) + expect(body.data[0].event_name).toBe('checkout') + expect(body.data[0].partner_name).toBe('ss-segment') + expect(body.data[0].advertiser_tracking_enabled).toBe(true) + expect(body.data[0].custom_data.np).toBe('ss-segment') + expect(body.data[0].user_data.em).toEqual(['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151']) + expect(body.data[0].user_data.fn).toEqual(['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d']) + expect(body.data[0].app_info.app_name).toBe('InitechGlobal') + expect(body.data[0].device_info.model).toBe('iPhone7,2') }) }) }) 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..d1e35857f33 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 @@ -25,6 +25,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.. */ @@ -126,6 +130,22 @@ export interface Payload { * 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 +160,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. + * opt_out_type is the field where we accept 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 } /** * The app store app ID. @@ -161,7 +197,7 @@ export interface Payload { */ device_brand?: string /** - * User device’s mobile carrier. + * User device's mobile carrier. */ device_carrier?: string /** @@ -184,4 +220,146 @@ export interface Payload { * Two-character ISO-639-1 language code indicating the user's language. */ language?: 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. Unix timestamp in seconds. + */ + install_time?: 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 + } } 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..501fb6f106d 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -20,15 +20,28 @@ const action: ActionDefinition = { type: 'string', required: true, choices: [ + { label: 'Add Payment Info', value: 'add_payment_info' }, { label: 'Add to Cart', value: 'add_to_cart' }, + { label: 'Add to Wishlist', value: 'add_to_wishlist' }, + { label: 'App Install', value: 'app_install' }, + { label: 'App Open', value: 'app_open' }, { label: 'Checkout', value: 'checkout' }, + { label: 'Contact', value: 'contact' }, + { label: 'Custom', value: 'custom' }, + { label: 'Customize Product', value: 'customize_product' }, + { label: 'Find Location', value: 'find_location' }, + { label: 'Initiate Checkout', value: 'initiate_checkout' }, { label: 'Lead', value: 'lead' }, { label: 'Page Visit', value: 'page_visit' }, + { label: 'Schedule', value: 'schedule' }, { label: 'Search', value: 'search' }, { label: 'Sign Up', value: 'signup' }, + { label: 'Start Trial', value: 'start_trial' }, + { label: 'Submit Application', value: 'submit_application' }, + { label: 'Subscribe', value: 'subscribe' }, { label: 'View Category', value: 'view_category' }, - { label: 'Watch Video', value: 'watch_video' }, - { label: 'Custom', value: 'custom' } + { label: 'View Content', value: 'view_content' }, + { label: 'Watch Video', value: 'watch_video' } ] }, action_source: { @@ -80,6 +93,14 @@ 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, app_id: { @@ -117,7 +138,7 @@ const action: ActionDefinition = { }, device_carrier: { label: 'Device Carrier', - description: 'User device’s mobile carrier. ', + description: "User device's mobile carrier.", type: 'string', default: { '@path': 'context.device.carrier' @@ -159,6 +180,206 @@ const action: ActionDefinition = { label: 'Language', description: "Two-character ISO-639-1 language code indicating the user's language.", type: 'string' + }, + app_info: { + label: 'App Info', + description: 'Object containing information about the application where event occurred.', + type: 'object', + 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: 'integer', + description: 'App install time. Unix timestamp in seconds.' + }, + 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_id: { '@path': '$.context.app.id' }, + app_name: { '@path': '$.context.app.name' }, + app_package_name: { '@path': '$.context.app.namespace' }, + app_version: { '@path': '$.context.app.version' }, + user_agent: { '@path': '$.context.userAgent' } + } + }, + device_info: { + label: 'Device Info', + description: 'Object containing information about the device where event occurred.', + type: 'object', + 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).' + }, + 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.' + }, + 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).' + }, + os_name: { + label: 'OS Name', + type: 'string', + description: 'Short name of the OS.' + }, + 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.' + }, + 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.brand' }, + carrier: { '@path': '$.context.device.carrier' }, + model: { '@path': '$.context.device.model' }, + type: { '@path': '$.context.device.type' }, + os_family: { '@path': '$.context.os.name' }, + 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' } + } } }, perform: async (request, { settings, payload }) => { @@ -197,6 +418,7 @@ 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, @@ -209,16 +431,26 @@ function createPinterestPayload(payload: Payload) { 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 + 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 }, app_id: payload.app_id, app_name: payload.app_name, app_version: payload.app_version, + app_info: payload.app_info, 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, + device_info: payload.device_info, wifi: payload.wifi, language: payload.language } From f589a84afa1e574010ac1ea17fcbaa2e27e3f4ad Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 12:10:00 +0100 Subject: [PATCH 02/16] tweaks to default mappings --- .../reportConversionEvent/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 501fb6f106d..42933d9a652 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -233,11 +233,13 @@ const action: ActionDefinition = { } }, default: { - app_id: { '@path': '$.context.app.id' }, + app_id: { '@path': '$.context.app.build' }, app_name: { '@path': '$.context.app.name' }, app_package_name: { '@path': '$.context.app.namespace' }, app_version: { '@path': '$.context.app.version' }, - user_agent: { '@path': '$.context.userAgent' } + user_agent: { '@path': '$.context.userAgent' }, + window_height: { '@path': '$.context.screen.height' }, + window_width: { '@path': '$.context.screen.width' } } }, device_info: { @@ -368,8 +370,8 @@ const action: ActionDefinition = { } }, default: { - brand: { '@path': '$.context.device.brand' }, - carrier: { '@path': '$.context.device.carrier' }, + brand: { '@path': '$.context.device.manufacturer' }, + carrier: { '@path': '$.context.network.carrier' }, model: { '@path': '$.context.device.model' }, type: { '@path': '$.context.device.type' }, os_family: { '@path': '$.context.os.name' }, From 1942c9c41dda86d261d5abadb04efc85e064993b Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 12:29:07 +0100 Subject: [PATCH 03/16] fix default mappings and install_time type - Map app_info.app_id to context.app.build - Map device_info.brand to context.device.manufacturer - Map device_info.carrier to context.network.carrier - Add window_height/window_width defaults to app_info - Change install_time to datetime with unix conversion Co-Authored-By: Claude Opus 4.6 --- .../__tests__/__snapshots__/index.test.ts.snap | 6 ++++++ .../reportConversionEvent/generated-types.ts | 4 ++-- .../reportConversionEvent/index.ts | 14 ++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) 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 index b625dfb7d90..8dc11a32c41 100644 --- 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 @@ -8,9 +8,11 @@ Object { "advertiser_tracking_enabled": true, "app_id": undefined, "app_info": Object { + "app_id": "3.0.1.545", "app_name": "InitechGlobal", "app_package_name": "com.production.segment", "app_version": "545", + "install_time": undefined, "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", }, "app_name": "InitechGlobal", @@ -33,6 +35,7 @@ Object { "device_brand": undefined, "device_carrier": undefined, "device_info": Object { + "brand": "Apple", "model": "iPhone7,2", "os_family": "iPhone OS", "os_version": "8.1.3", @@ -105,9 +108,11 @@ Object { "advertiser_tracking_enabled": true, "app_id": undefined, "app_info": Object { + "app_id": "3.0.1.545", "app_name": "InitechGlobal", "app_package_name": "com.production.segment", "app_version": "545", + "install_time": undefined, "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", }, "app_name": "InitechGlobal", @@ -130,6 +135,7 @@ Object { "device_brand": undefined, "device_carrier": undefined, "device_info": Object { + "brand": "Apple", "model": "iPhone7,2", "os_family": "iPhone OS", "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 d1e35857f33..8106c47946d 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 @@ -245,9 +245,9 @@ export interface Payload { */ app_version?: string /** - * App install time. Unix timestamp in seconds. + * App install time. Will be converted to Unix timestamp in seconds before sending. */ - install_time?: number + install_time?: string | number /** * User Agent request header. */ 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 42933d9a652..fb05e7d004a 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -213,8 +213,8 @@ const action: ActionDefinition = { }, install_time: { label: 'Install Time', - type: 'integer', - description: 'App install time. Unix timestamp in seconds.' + type: 'datetime', + description: 'App install time. Will be converted to Unix timestamp in seconds before sending.' }, user_agent: { label: 'User Agent', @@ -306,7 +306,8 @@ const action: ActionDefinition = { network_type: { label: 'Network Type', type: 'string', - description: 'Network type (wifi, cellular_2g, cellular_3g, cellular_4g, cellular_5g, cellular_6g, ethernet, unknown).' + description: + 'Network type (wifi, cellular_2g, cellular_3g, cellular_4g, cellular_5g, cellular_6g, ethernet, unknown).' }, os_family: { label: 'OS Family', @@ -446,7 +447,12 @@ function createPinterestPayload(payload: Payload) { app_id: payload.app_id, app_name: payload.app_name, app_version: payload.app_version, - app_info: payload.app_info, + app_info: payload.app_info + ? { + ...payload.app_info, + install_time: payload.app_info.install_time ? dayjs.utc(payload.app_info.install_time).unix() : undefined + } + : undefined, device_brand: payload.device_brand, device_carrier: payload.device_carrier, device_model: payload.device_model, From 5c7733bdf9b65fc3ddc77fba310b5eb9191cdc55 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 12:34:24 +0100 Subject: [PATCH 04/16] build app_info externally and omit if empty Co-Authored-By: Claude Opus 4.6 --- .../reportConversionEvent/index.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 fb05e7d004a..88d5ef69737 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -411,6 +411,15 @@ async function processPayload(request: RequestClient, settings: Settings, payloa }) } +function buildAppInfo(payload: Payload) { + const appInfo = { + ...payload.app_info, + install_time: payload.app_info?.install_time ? dayjs.utc(payload.app_info.install_time).unix() : undefined + } + const hasContent = Object.values(appInfo).some((v) => v !== undefined && v !== null) + return hasContent ? appInfo : undefined +} + function createPinterestPayload(payload: Payload) { return [ { @@ -447,12 +456,7 @@ function createPinterestPayload(payload: Payload) { app_id: payload.app_id, app_name: payload.app_name, app_version: payload.app_version, - app_info: payload.app_info - ? { - ...payload.app_info, - install_time: payload.app_info.install_time ? dayjs.utc(payload.app_info.install_time).unix() : undefined - } - : undefined, + app_info: buildAppInfo(payload), device_brand: payload.device_brand, device_carrier: payload.device_carrier, device_model: payload.device_model, From 30845f78e3b9f58c75b6d8946277a031576ac5a8 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 12:43:27 +0100 Subject: [PATCH 05/16] restore full body assertion for hashed data test Co-Authored-By: Claude Opus 4.6 --- .../__tests__/index.test.ts | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) 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 87c7effbcff..d8f54936b50 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 @@ -217,16 +217,63 @@ describe('PinterestConversionApi', () => { }) expect(responses.length).toBe(1) expect(responses[0].status).toBe(200) - expect(JSON.parse(responses[0]?.options?.body as string)?.data?.length).toBe(1) const body = JSON.parse(responses[0].options.body as string) - expect(body.data[0].event_name).toBe('checkout') - expect(body.data[0].partner_name).toBe('ss-segment') - expect(body.data[0].advertiser_tracking_enabled).toBe(true) - expect(body.data[0].custom_data.np).toBe('ss-segment') - expect(body.data[0].user_data.em).toEqual(['c551027f06bd3f307ccd6abb61edc500def2680944c010e932ab5b27a3a8f151']) - expect(body.data[0].user_data.fn).toEqual(['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d']) - expect(body.data[0].app_info.app_name).toBe('InitechGlobal') - expect(body.data[0].device_info.model).toBe('iPhone7,2') + expect(body.data.length).toBe(1) + expect(body.data[0]).toEqual({ + 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, + advertiser_tracking_enabled: 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, + np: 'ss-segment' + }, + app_name: 'InitechGlobal', + app_version: '545', + app_info: { + app_id: '3.0.1.545', + 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' + }, + device_model: 'iPhone7,2', + device_type: 'ios', + os_version: '8.1.3', + device_info: { + brand: 'Apple', + model: 'iPhone7,2', + type: 'ios', + os_family: 'iPhone OS', + os_version: '8.1.3', + timezone: 'Europe/Amsterdam' + } + }) }) }) }) From 70cf765bb5746a3afb2f78c39be78bde97df1199 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 13:09:34 +0100 Subject: [PATCH 06/16] add data_format toggle to switch between legacy and structured fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a `data_format` choice field that controls field visibility: - "Structured Fields" (default for new instances): shows flat custom data fields, contents array, app_info, and device_info objects - "Legacy Fields" (or undefined for existing instances): shows the original nested custom_data object and flat app/device fields Existing integrations are unaffected — they have no value for data_format (undefined), which is treated as legacy via depends_on conditions. Co-Authored-By: Claude Opus 4.6 --- .../pinterest-capi-custom-data.ts | 12 +- .../__snapshots__/index.test.ts.snap | 201 --------- .../__tests__/index.test.ts | 420 +++++++++++------- .../reportConversionEvent/generated-types.ts | 93 +++- .../reportConversionEvent/index.ts | 323 +++++++++++--- 5 files changed, 631 insertions(+), 418 deletions(-) delete mode 100644 packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/__snapshots__/index.test.ts.snap 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 6631429ce20..549fc8d2607 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,11 @@ -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 custom_data_field = (dependsOn: DependsOnConditions): InputField => ({ + label: '[Legacy] Custom Data', + description: + 'Object containing custom event data. This is the legacy format — use the new individual fields (Custom Data, Contents) when "Use Structured Fields" is selected.', type: 'object', + depends_on: dependsOn, properties: { currency: { label: 'Currency', @@ -125,4 +127,4 @@ export const custom_data_field: InputField = { '@path': '$.properties.currency' } } -} +}) 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 8dc11a32c41..00000000000 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/__tests__/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,201 +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", - "advertiser_tracking_enabled": true, - "app_id": undefined, - "app_info": Object { - "app_id": "3.0.1.545", - "app_name": "InitechGlobal", - "app_package_name": "com.production.segment", - "app_version": "545", - "install_time": undefined, - "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", - }, - "app_name": "InitechGlobal", - "app_version": "545", - "custom_data": Object { - "content_brand": undefined, - "content_category": undefined, - "content_ids": undefined, - "content_name": undefined, - "contents": undefined, - "currency": undefined, - "np": "ss-segment", - "num_items": 2, - "opt_out_type": undefined, - "order_id": undefined, - "predicted_ltv": undefined, - "search_string": undefined, - "value": "2000", - }, - "device_brand": undefined, - "device_carrier": undefined, - "device_info": Object { - "brand": "Apple", - "model": "iPhone7,2", - "os_family": "iPhone OS", - "os_version": "8.1.3", - "timezone": "Europe/Amsterdam", - "type": "ios", - }, - "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", - "advertiser_tracking_enabled": true, - "app_id": undefined, - "app_info": Object { - "app_id": "3.0.1.545", - "app_name": "InitechGlobal", - "app_package_name": "com.production.segment", - "app_version": "545", - "install_time": undefined, - "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", - }, - "app_name": "InitechGlobal", - "app_version": "545", - "custom_data": Object { - "content_brand": undefined, - "content_category": undefined, - "content_ids": undefined, - "content_name": undefined, - "contents": undefined, - "currency": undefined, - "np": "ss-segment", - "num_items": undefined, - "opt_out_type": undefined, - "order_id": undefined, - "predicted_ltv": undefined, - "search_string": undefined, - "value": undefined, - }, - "device_brand": undefined, - "device_carrier": undefined, - "device_info": Object { - "brand": "Apple", - "model": "iPhone7,2", - "os_family": "iPhone OS", - "os_version": "8.1.3", - "timezone": "Europe/Amsterdam", - "type": "ios", - }, - "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 d8f54936b50..7c50b9b2241 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,138 +99,290 @@ 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, {}) + 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: { - 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' + 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].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() }) - 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, {}) + 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: { - 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 + 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() }) - 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]).toEqual({ - 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, - advertiser_tracking_enabled: 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, - np: 'ss-segment' - }, - app_name: 'InitechGlobal', - app_version: '545', - app_info: { + + 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() + }) + }) + + describe('structured mode', () => { + it('should send event using structured 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: 'structured', + 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' + }, + 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].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_id: '3.0.1.545', 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' - }, - device_model: 'iPhone7,2', - device_type: 'ios', - os_version: '8.1.3', - device_info: { + }) + expect(body.data[0].device_info).toEqual({ brand: 'Apple', model: 'iPhone7,2', type: 'ios', os_family: 'iPhone OS', 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 structured 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: 'structured', + 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' + }, + 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') }) }) }) 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 8106c47946d..36180a73435 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 { + /** + * Controls which fields are displayed. "Structured Fields" uses the new app_info, device_info, and flat custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device 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). */ @@ -99,7 +103,7 @@ export interface Payload { partner_id?: string | null } /** - * Object containing customer information data. + * Object containing custom event data. This is the legacy format — use the new individual fields (Custom Data, Contents) when "Use Structured Fields" is selected. */ custom_data?: { /** @@ -187,7 +191,7 @@ export interface Payload { /** * Name of the app. */ - app_name: string + app_name?: string /** * Version of the app. */ @@ -213,13 +217,82 @@ export interface Payload { */ os_version?: string /** - * Whether the event occurred when the user device was connected to wifi. + * ISO-4217 currency code. If not provided, it will default to the currency set for the ad account. */ - wifi?: boolean + currency?: string /** - * Two-character ISO-639-1 language code indicating the user's language. + * 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. */ - language?: string + value?: number + /** + * Product IDs as an array of strings. + */ + content_ids?: string[] + /** + * 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 + }[] + /** + * 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 /** * Object containing information about the application where event occurred. */ @@ -362,4 +435,12 @@ export interface Payload { */ type?: string } + /** + * Whether the event occurred when the user device was connected to wifi. + */ + wifi?: boolean + /** + * Two-character ISO-639-1 language code indicating the user's language. + */ + language?: string } 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 88d5ef69737..9f765342516 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -1,5 +1,6 @@ import type { ActionDefinition, RequestClient } from '@segment/actions-core' import { IntegrationError } from '@segment/actions-core' +import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' import { API_VERSION, PARTNER_NAME } from '../constants' import type { Settings } from '../generated-types' import { custom_data_field } from '../pinterest-capi-custom-data' @@ -8,11 +9,34 @@ import type { Payload } from './generated-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_STRUCTURED: DependsOnConditions = { + conditions: [{ fieldKey: 'data_format', operator: 'is', value: 'structured' }] +} + 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: 'Data Format', + description: + 'Controls which fields are displayed. "Structured Fields" uses the new app_info, device_info, and flat custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device fields.', + type: 'string', + choices: [ + { label: 'Structured Fields', value: 'structured' }, + { label: 'Legacy Fields', value: 'legacy' } + ], + default: 'structured' + }, event_name: { label: 'Event Name', description: @@ -102,89 +126,236 @@ const action: ActionDefinition = { } }, user_data: user_data_field, - custom_data: custom_data_field, + + // --- Legacy fields (shown when data_format is 'legacy' or undefined) --- + custom_data: custom_data_field(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, + 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', + 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' } }, - wifi: { - label: 'Wifi', - description: 'Whether the event occurred when the user device was connected to wifi.', - type: 'boolean', + + // --- Structured fields (shown when data_format is 'structured') --- + currency: { + label: 'Currency', + description: 'ISO-4217 currency code. If not provided, it will default to the currency set for the ad account.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED, default: { - '@path': '$.context.network.wifi' + '@path': '$.properties.currency' } }, - language: { - label: 'Language', - description: "Two-character ISO-639-1 language code indicating the user's language.", - type: 'string' + value: { + label: 'Value', + 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.', + type: 'number', + depends_on: DEPENDS_ON_STRUCTURED, + default: { + '@if': { + exists: { '@path': '$.properties.price' }, + then: { '@path': '$.properties.price' }, + else: { '@path': '$.properties.value' } + } + } + }, + content_ids: { + label: 'Content IDs', + description: 'Product IDs as an array of strings.', + type: 'string', + multiple: true, + depends_on: DEPENDS_ON_STRUCTURED, + default: { + '@path': '$.properties.content_ids' + } + }, + contents: { + label: 'Contents', + description: 'A list of objects containing information about products.', + type: 'object', + multiple: true, + depends_on: DEPENDS_ON_STRUCTURED, + 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' } + } + ] + } + }, + num_items: { + label: 'Number of Items', + description: 'Total number of products in the event.', + type: 'integer', + depends_on: DEPENDS_ON_STRUCTURED, + default: { + '@path': '$.properties.num_items' + } + }, + order_id: { + label: 'Order ID', + description: 'The order ID.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED, + default: { + '@path': '$.properties.order_id' + } + }, + search_string: { + label: 'Search String', + description: 'Search string related to the conversion event.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED, + default: { + '@path': '$.properties.query' + } + }, + opt_out_type: { + label: 'Opt Out Type', + description: + "The field where Pinterest accepts opt outs for your users' privacy preference. It can handle multiple values with commas separated.", + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED + }, + content_brand: { + label: 'Content Brand', + description: 'The brand of the content associated with the event.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED + }, + content_category: { + label: 'Content Category', + description: 'The category of the content associated with the event.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED + }, + content_name: { + label: 'Content Name', + description: 'The name of the page or product associated with the event.', + type: 'string', + depends_on: DEPENDS_ON_STRUCTURED + }, + predicted_ltv: { + label: 'Predicted LTV', + description: 'Predicted lifetime value of user associated with the event.', + type: 'number', + depends_on: DEPENDS_ON_STRUCTURED }, app_info: { label: 'App Info', description: 'Object containing information about the application where event occurred.', type: 'object', + depends_on: DEPENDS_ON_STRUCTURED, properties: { app_id: { label: 'App ID', @@ -246,6 +417,7 @@ const action: ActionDefinition = { label: 'Device Info', description: 'Object containing information about the device where event occurred.', type: 'object', + depends_on: DEPENDS_ON_STRUCTURED, properties: { battery_level: { label: 'Battery Level', @@ -383,12 +555,28 @@ const action: ActionDefinition = { 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.', + type: 'boolean', + default: { + '@path': '$.context.network.wifi' + } + }, + language: { + label: 'Language', + description: "Two-character ISO-639-1 language code indicating the user's language.", + type: 'string' } }, perform: async (request, { settings, payload }) => { return processPayload(request, settings, payload) } } + async function processPayload(request: RequestClient, settings: Settings, payload: Payload) { if ( isEmpty(payload.user_data?.email) && @@ -420,7 +608,60 @@ function buildAppInfo(payload: Payload) { 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) { + const isStructured = payload.data_format === 'structured' + + if (isStructured) { + return { + currency: payload.currency, + value: typeof payload.value === 'number' ? String(payload.value) : undefined, + content_ids: payload.content_ids, + contents: payload.contents?.map((item) => ({ + ...item, + item_price: typeof item.item_price === 'number' ? String(item.item_price) : undefined + })), + num_items: payload.num_items, + order_id: payload.order_id, + search_string: payload.search_string, + opt_out_type: payload.opt_out_type, + content_brand: payload.content_brand, + content_category: payload.content_category, + content_name: payload.content_name, + predicted_ltv: typeof payload.predicted_ltv === 'number' ? String(payload.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) { + const isStructured = payload.data_format === 'structured' + return [ { event_name: payload.event_name, @@ -432,37 +673,17 @@ function createPinterestPayload(payload: Payload) { 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, - 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 - }, - app_id: payload.app_id, - app_name: payload.app_name, - app_version: payload.app_version, - app_info: buildAppInfo(payload), - 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, - device_info: payload.device_info, + 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 } From 23c2f35d5867c973bbad3fde7e5b27c5ff1d0923 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 13:23:08 +0100 Subject: [PATCH 07/16] extract custom_data_2 and contents as separate structured fields - custom_data_2: object field with all custom data properties except contents - contents: standalone array field with proper @arrayPath default mapping - Both defined with depends_on DEPENDS_ON_STRUCTURED - custom_data_field_2 exported from pinterest-capi-custom-data.ts Co-Authored-By: Claude Opus 4.6 --- .../pinterest-capi-custom-data.ts | 79 ++++++++++++ .../__tests__/index.test.ts | 14 +- .../reportConversionEvent/generated-types.ts | 89 +++++++------ .../reportConversionEvent/index.ts | 120 +++--------------- 4 files changed, 152 insertions(+), 150 deletions(-) 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 549fc8d2607..7e68a16ddf9 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 @@ -128,3 +128,82 @@ export const custom_data_field = (dependsOn: DependsOnConditions): InputField => } } }) + +export const custom_data_field_2 = (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' } + } +}) 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 7c50b9b2241..a15a1562205 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 @@ -282,9 +282,11 @@ describe('PinterestConversionApi', () => { 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' }, - value: 2000, - num_items: 2, - currency: 'USD', + custom_data_2: { + value: 2000, + num_items: 2, + currency: 'USD' + }, contents: [ { id: 'sku_123', @@ -368,8 +370,10 @@ describe('PinterestConversionApi', () => { 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' }, - value: 2000, - num_items: 2 + custom_data_2: { + value: 2000, + num_items: 2 + } } }) expect(responses.length).toBe(1) 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 36180a73435..b574b14e798 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 @@ -217,17 +217,54 @@ export interface Payload { */ os_version?: string /** - * ISO-4217 currency code. If not provided, it will default to the currency set for the ad account. + * Object containing custom event data. */ - 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[] + 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. */ @@ -261,38 +298,6 @@ export interface Payload { */ item_name?: 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 /** * Object containing information about the application where event occurred. */ 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 9f765342516..a84c5cc8eb6 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -3,7 +3,7 @@ import { IntegrationError } from '@segment/actions-core' import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' import { API_VERSION, PARTNER_NAME } from '../constants' import type { Settings } from '../generated-types' -import { custom_data_field } from '../pinterest-capi-custom-data' +import { custom_data_field, custom_data_field_2 } from '../pinterest-capi-custom-data' import { user_data_field, hash_user_data } from '../pinterset-capi-user-data' import type { Payload } from './generated-types' import isEmpty from 'lodash/isEmpty' @@ -142,6 +142,7 @@ const action: ActionDefinition = { label: '[Legacy] App Name', description: 'Name of the app.', type: 'string', + required: DEPENDS_ON_LEGACY, depends_on: DEPENDS_ON_LEGACY, default: { '@path': '$.context.app.name' @@ -203,39 +204,7 @@ const action: ActionDefinition = { }, // --- Structured fields (shown when data_format is 'structured') --- - currency: { - label: 'Currency', - description: 'ISO-4217 currency code. If not provided, it will default to the currency set for the ad account.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@path': '$.properties.currency' - } - }, - value: { - label: 'Value', - 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.', - type: 'number', - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@if': { - exists: { '@path': '$.properties.price' }, - then: { '@path': '$.properties.price' }, - else: { '@path': '$.properties.value' } - } - } - }, - content_ids: { - label: 'Content IDs', - description: 'Product IDs as an array of strings.', - type: 'string', - multiple: true, - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@path': '$.properties.content_ids' - } - }, + custom_data_2: custom_data_field_2(DEPENDS_ON_STRUCTURED), contents: { label: 'Contents', description: 'A list of objects containing information about products.', @@ -293,64 +262,6 @@ const action: ActionDefinition = { ] } }, - num_items: { - label: 'Number of Items', - description: 'Total number of products in the event.', - type: 'integer', - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@path': '$.properties.num_items' - } - }, - order_id: { - label: 'Order ID', - description: 'The order ID.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@path': '$.properties.order_id' - } - }, - search_string: { - label: 'Search String', - description: 'Search string related to the conversion event.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED, - default: { - '@path': '$.properties.query' - } - }, - opt_out_type: { - label: 'Opt Out Type', - description: - "The field where Pinterest accepts opt outs for your users' privacy preference. It can handle multiple values with commas separated.", - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED - }, - content_brand: { - label: 'Content Brand', - description: 'The brand of the content associated with the event.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED - }, - content_category: { - label: 'Content Category', - description: 'The category of the content associated with the event.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED - }, - content_name: { - label: 'Content Name', - description: 'The name of the page or product associated with the event.', - type: 'string', - depends_on: DEPENDS_ON_STRUCTURED - }, - predicted_ltv: { - label: 'Predicted LTV', - description: 'Predicted lifetime value of user associated with the event.', - type: 'number', - depends_on: DEPENDS_ON_STRUCTURED - }, app_info: { label: 'App Info', description: 'Object containing information about the application where event occurred.', @@ -619,21 +530,24 @@ function buildCustomData(payload: Payload) { if (isStructured) { return { - currency: payload.currency, - value: typeof payload.value === 'number' ? String(payload.value) : undefined, - content_ids: payload.content_ids, + 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.num_items, - order_id: payload.order_id, - search_string: payload.search_string, - opt_out_type: payload.opt_out_type, - content_brand: payload.content_brand, - content_category: payload.content_category, - content_name: payload.content_name, - predicted_ltv: typeof payload.predicted_ltv === 'number' ? String(payload.predicted_ltv) : 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 } } From 5bd93607ba0fd87aa164082bf1077c2f633fc940 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 13:29:44 +0100 Subject: [PATCH 08/16] move contents_field to pinterest-capi-custom-data.ts Co-Authored-By: Claude Opus 4.6 --- .../pinterest-capi-custom-data.ts | 61 ++++++++++++++++++- .../reportConversionEvent/index.ts | 60 +----------------- 2 files changed, 61 insertions(+), 60 deletions(-) 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 7e68a16ddf9..3c09b6d2eab 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 @@ -2,8 +2,7 @@ import { InputField, DependsOnConditions } from '@segment/actions-core/destinati export const custom_data_field = (dependsOn: DependsOnConditions): InputField => ({ label: '[Legacy] Custom Data', - description: - 'Object containing custom event data. This is the legacy format — use the new individual fields (Custom Data, Contents) when "Use Structured Fields" is selected.', + description: 'Object containing custom event data.', type: 'object', depends_on: dependsOn, properties: { @@ -207,3 +206,61 @@ export const custom_data_field_2 = (dependsOn: DependsOnConditions): InputField search_string: { '@path': '$.properties.query' } } }) + +export const contents_field = (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/index.ts b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts index a84c5cc8eb6..90bd5662419 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -3,7 +3,7 @@ import { IntegrationError } from '@segment/actions-core' import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' import { API_VERSION, PARTNER_NAME } from '../constants' import type { Settings } from '../generated-types' -import { custom_data_field, custom_data_field_2 } from '../pinterest-capi-custom-data' +import { custom_data_field, custom_data_field_2, contents_field } from '../pinterest-capi-custom-data' import { user_data_field, hash_user_data } from '../pinterset-capi-user-data' import type { Payload } from './generated-types' import isEmpty from 'lodash/isEmpty' @@ -205,63 +205,7 @@ const action: ActionDefinition = { // --- Structured fields (shown when data_format is 'structured') --- custom_data_2: custom_data_field_2(DEPENDS_ON_STRUCTURED), - contents: { - label: 'Contents', - description: 'A list of objects containing information about products.', - type: 'object', - multiple: true, - depends_on: DEPENDS_ON_STRUCTURED, - 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' } - } - ] - } - }, + contents: contents_field(DEPENDS_ON_STRUCTURED), app_info: { label: 'App Info', description: 'Object containing information about the application where event occurred.', From 94688779b4084d7188501118f76e293ce8ede9be Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 13:42:21 +0100 Subject: [PATCH 09/16] rename to latest, rename field factories to getX pattern Co-Authored-By: Claude Opus 4.6 --- .../pinterest-capi-custom-data.ts | 6 ++-- .../__tests__/index.test.ts | 10 +++---- .../reportConversionEvent/index.ts | 28 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) 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 3c09b6d2eab..2d7fcf8b353 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,6 +1,6 @@ import { InputField, DependsOnConditions } from '@segment/actions-core/destination-kit/types' -export const custom_data_field = (dependsOn: DependsOnConditions): InputField => ({ +export const getCustomDataField = (dependsOn: DependsOnConditions): InputField => ({ label: '[Legacy] Custom Data', description: 'Object containing custom event data.', type: 'object', @@ -128,7 +128,7 @@ export const custom_data_field = (dependsOn: DependsOnConditions): InputField => } }) -export const custom_data_field_2 = (dependsOn: DependsOnConditions): InputField => ({ +export const getCustomDataField2 = (dependsOn: DependsOnConditions): InputField => ({ label: 'Custom Data', description: 'Object containing custom event data.', type: 'object', @@ -207,7 +207,7 @@ export const custom_data_field_2 = (dependsOn: DependsOnConditions): InputField } }) -export const contents_field = (dependsOn: DependsOnConditions): InputField => ({ +export const getContentsField = (dependsOn: DependsOnConditions): InputField => ({ label: 'Contents', description: 'A list of objects containing information about products.', type: 'object', 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 a15a1562205..7f8e23ad124 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 @@ -250,8 +250,8 @@ describe('PinterestConversionApi', () => { }) }) - describe('structured mode', () => { - it('should send event using structured fields with app_info and device_info', async () => { + 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, {}) @@ -261,7 +261,7 @@ describe('PinterestConversionApi', () => { settings: authData, useDefaultMappings: true, mapping: { - data_format: 'structured', + data_format: 'latest', event_name: 'checkout', user_data: { first_name: ['Gaurav'], @@ -339,7 +339,7 @@ describe('PinterestConversionApi', () => { expect(body.data[0].device_model).toBeUndefined() }) - it('should detect pre-hashed data in structured mode', async () => { + 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, {}) @@ -349,7 +349,7 @@ describe('PinterestConversionApi', () => { settings: authData, useDefaultMappings: true, mapping: { - data_format: 'structured', + data_format: 'latest', event_name: 'checkout', user_data: { first_name: ['44104fcaef8476724152090d6d7bd9afa8ca5b385f6a99d3c6cf36b943b9872d'], 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 90bd5662419..6dfc5c8c097 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -3,7 +3,7 @@ import { IntegrationError } from '@segment/actions-core' import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' import { API_VERSION, PARTNER_NAME } from '../constants' import type { Settings } from '../generated-types' -import { custom_data_field, custom_data_field_2, contents_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 isEmpty from 'lodash/isEmpty' @@ -17,8 +17,8 @@ const DEPENDS_ON_LEGACY: DependsOnConditions = { ] } -const DEPENDS_ON_STRUCTURED: DependsOnConditions = { - conditions: [{ fieldKey: 'data_format', operator: 'is', value: 'structured' }] +const DEPENDS_ON_LATEST: DependsOnConditions = { + conditions: [{ fieldKey: 'data_format', operator: 'is', value: 'latest' }] } const action: ActionDefinition = { @@ -29,13 +29,13 @@ const action: ActionDefinition = { data_format: { label: 'Data Format', description: - 'Controls which fields are displayed. "Structured Fields" uses the new app_info, device_info, and flat custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device fields.', + 'Controls which fields are displayed. "Latest Fields" uses the new app_info, device_info, and custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device fields.', type: 'string', choices: [ - { label: 'Structured Fields', value: 'structured' }, + { label: 'Latest Fields', value: 'latest' }, { label: 'Legacy Fields', value: 'legacy' } ], - default: 'structured' + default: 'latest' }, event_name: { label: 'Event Name', @@ -128,7 +128,7 @@ const action: ActionDefinition = { user_data: user_data_field, // --- Legacy fields (shown when data_format is 'legacy' or undefined) --- - custom_data: custom_data_field(DEPENDS_ON_LEGACY), + custom_data: getCustomDataField(DEPENDS_ON_LEGACY), app_id: { label: '[Legacy] App ID', description: 'The app store app ID.', @@ -203,14 +203,14 @@ const action: ActionDefinition = { } }, - // --- Structured fields (shown when data_format is 'structured') --- - custom_data_2: custom_data_field_2(DEPENDS_ON_STRUCTURED), - contents: contents_field(DEPENDS_ON_STRUCTURED), + // --- 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', - depends_on: DEPENDS_ON_STRUCTURED, + depends_on: DEPENDS_ON_LATEST, properties: { app_id: { label: 'App ID', @@ -272,7 +272,7 @@ const action: ActionDefinition = { label: 'Device Info', description: 'Object containing information about the device where event occurred.', type: 'object', - depends_on: DEPENDS_ON_STRUCTURED, + depends_on: DEPENDS_ON_LATEST, properties: { battery_level: { label: 'Battery Level', @@ -470,7 +470,7 @@ function buildDeviceInfo(payload: Payload) { } function buildCustomData(payload: Payload) { - const isStructured = payload.data_format === 'structured' + const isStructured = payload.data_format === 'latest' if (isStructured) { return { @@ -518,7 +518,7 @@ function buildCustomData(payload: Payload) { } function createPinterestPayload(payload: Payload) { - const isStructured = payload.data_format === 'structured' + const isStructured = payload.data_format === 'latest' return [ { From 8c984d57c528598cb1642c6f7371d62f2c08934b Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 13:52:00 +0100 Subject: [PATCH 10/16] rename toggle label to Use Latest Fields with simpler description Co-Authored-By: Claude Opus 4.6 --- .../pinterest-conversions/reportConversionEvent/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6dfc5c8c097..9823c5bdef6 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -27,9 +27,9 @@ const action: ActionDefinition = { '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: 'Data Format', + label: 'Use Latest Fields', description: - 'Controls which fields are displayed. "Latest Fields" uses the new app_info, device_info, and custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device fields.', + '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' }, From 703ecb2a94040bba03dc70188ce11b2155fa8e66 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 14:10:11 +0100 Subject: [PATCH 11/16] types --- .../reportConversionEvent/generated-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b574b14e798..784d5249286 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 @@ -2,7 +2,7 @@ export interface Payload { /** - * Controls which fields are displayed. "Structured Fields" uses the new app_info, device_info, and flat custom data fields. "Legacy Fields" uses the original nested custom_data object and flat app/device fields. + * Switch between the latest field configuration and the legacy fields. New instances default to the latest fields. */ data_format?: string /** @@ -103,7 +103,7 @@ export interface Payload { partner_id?: string | null } /** - * Object containing custom event data. This is the legacy format — use the new individual fields (Custom Data, Contents) when "Use Structured Fields" is selected. + * Object containing custom event data. */ custom_data?: { /** From 528cfadb15eaf580cb4ee8e52cbb5dc4150bd5a7 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 14:28:07 +0100 Subject: [PATCH 12/16] more unit tests --- .../__tests__/index.test.ts | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) 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 7f8e23ad124..3d67c80dc6e 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 @@ -389,5 +389,177 @@ describe('PinterestConversionApi', () => { 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() + }) + }) }) }) From 896ccc4012aa0422eb3b9c16904f975e31dc1706 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 15:25:49 +0100 Subject: [PATCH 13/16] Copilot review changes --- .../__tests__/index.test.ts | 43 ++++++++++++++++++- .../reportConversionEvent/index.ts | 12 ++++-- 2 files changed, 50 insertions(+), 5 deletions(-) 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 3d67c80dc6e..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 @@ -141,6 +141,7 @@ describe('PinterestConversionApi', () => { 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') @@ -250,6 +251,45 @@ describe('PinterestConversionApi', () => { }) }) + 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`) @@ -303,6 +343,7 @@ describe('PinterestConversionApi', () => { 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') @@ -318,7 +359,6 @@ describe('PinterestConversionApi', () => { } ]) expect(body.data[0].app_info).toEqual({ - app_id: '3.0.1.545', app_name: 'InitechGlobal', app_package_name: 'com.production.segment', app_version: '545', @@ -329,7 +369,6 @@ describe('PinterestConversionApi', () => { brand: 'Apple', model: 'iPhone7,2', type: 'ios', - os_family: 'iPhone OS', os_version: '8.1.3', timezone: 'Europe/Amsterdam' }) 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 9823c5bdef6..e06e3770f80 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -259,7 +259,6 @@ const action: ActionDefinition = { } }, default: { - app_id: { '@path': '$.context.app.build' }, app_name: { '@path': '$.context.app.name' }, app_package_name: { '@path': '$.context.app.namespace' }, app_version: { '@path': '$.context.app.version' }, @@ -402,7 +401,6 @@ const action: ActionDefinition = { carrier: { '@path': '$.context.network.carrier' }, model: { '@path': '$.context.device.model' }, type: { '@path': '$.context.device.type' }, - os_family: { '@path': '$.context.os.name' }, os_version: { '@path': '$.context.os.version' }, locale: { '@path': '$.context.locale' }, screen_density: { '@path': '$.context.screen.density' }, @@ -454,10 +452,18 @@ async function processPayload(request: RequestClient, settings: Settings, payloa }) } +function convertInstallTime(value: string | number | undefined | null): number | undefined { + if (!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: payload.app_info?.install_time ? dayjs.utc(payload.app_info.install_time).unix() : undefined + install_time: convertInstallTime(payload.app_info?.install_time) } const hasContent = Object.values(appInfo).some((v) => v !== undefined && v !== null) return hasContent ? appInfo : undefined From a44dac8f5e5da6c0df157ace422772e7d23f3cd4 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 15:37:26 +0100 Subject: [PATCH 14/16] copilot fix --- .../pinterest-conversions/reportConversionEvent/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e06e3770f80..4a67c259bde 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -453,7 +453,7 @@ async function processPayload(request: RequestClient, settings: Settings, payloa } function convertInstallTime(value: string | number | undefined | null): number | undefined { - if (!value) return 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 From d268f8ae0a33d2da7b66d877405a914feff53b1c Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Wed, 3 Jun 2026 17:41:57 +0100 Subject: [PATCH 15/16] add Pinterest API payload types and address Copilot feedback - Define PinterestEventPayload, LegacyPinterestEventPayload, CustomData, UserData, ContentsItem, AppInfo, DeviceInfo in types.ts - Wire up return types on buildCustomData and createPinterestPayload - Fix convertInstallTime to handle 0 correctly - Remove app_id and os_family default mappings - Add advertiser_tracking_enabled assertions to tests - Add test for undefined data_format (existing subscriptions) Co-Authored-By: Claude Opus 4.6 --- .../reportConversionEvent/index.ts | 5 +- .../pinterest-conversions/types.ts | 129 ++++++++++++++++++ 2 files changed, 132 insertions(+), 2 deletions(-) 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 4a67c259bde..2e4e7265d6b 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -6,6 +6,7 @@ import type { Settings } from '../generated-types' 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' @@ -475,7 +476,7 @@ function buildDeviceInfo(payload: Payload) { return hasContent ? payload.device_info : undefined } -function buildCustomData(payload: Payload) { +function buildCustomData(payload: Payload): CustomData { const isStructured = payload.data_format === 'latest' if (isStructured) { @@ -523,7 +524,7 @@ function buildCustomData(payload: Payload) { } } -function createPinterestPayload(payload: Payload) { +function createPinterestPayload(payload: Payload): (PinterestEventPayload | LegacyPinterestEventPayload)[] { const isStructured = payload.data_format === 'latest' return [ 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 +} From 2dc3c324c1d3193840f58f883e4634725a54b279 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 4 Jun 2026 09:25:49 +0100 Subject: [PATCH 16/16] addressing Copilot comments --- .../pinterest-conversions/index.ts | 98 +++++++++++++++++-- .../pinterest-capi-custom-data.ts | 6 +- .../reportConversionEvent/generated-types.ts | 4 +- .../reportConversionEvent/index.ts | 46 ++++----- 4 files changed, 117 insertions(+), 37 deletions(-) diff --git a/packages/destination-actions/src/destinations/pinterest-conversions/index.ts b/packages/destination-actions/src/destinations/pinterest-conversions/index.ts index f8e574de627..fa29bb3c555 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,13 +66,53 @@ 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' }, @@ -82,7 +122,17 @@ const destination: DestinationDefinition = { 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 2d7fcf8b353..1c7ea6a880a 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 @@ -40,9 +40,9 @@ export const getCustomDataField = (dependsOn: DependsOnConditions): 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', @@ -84,7 +84,7 @@ export const getCustomDataField = (dependsOn: DependsOnConditions): 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: { 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 784d5249286..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 @@ -131,7 +131,7 @@ export interface Payload { */ item_price?: number /** - * The number of items purchased + * The number of items purchased. */ quantity?: number /** @@ -164,7 +164,7 @@ 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 /** 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 2e4e7265d6b..984189ca96e 100644 --- a/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/pinterest-conversions/reportConversionEvent/index.ts @@ -1,7 +1,7 @@ import type { ActionDefinition, RequestClient } from '@segment/actions-core' import { IntegrationError } from '@segment/actions-core' import type { DependsOnConditions } from '@segment/actions-core/destination-kit/types' -import { API_VERSION, PARTNER_NAME } from '../constants' +import { API_VERSION, PARTNER_NAME, EVENT_NAME } from '../constants' import type { Settings } from '../generated-types' import { getCustomDataField, getCustomDataField2, getContentsField } from '../pinterest-capi-custom-data' import { user_data_field, hash_user_data } from '../pinterset-capi-user-data' @@ -45,28 +45,28 @@ const action: ActionDefinition = { type: 'string', required: true, choices: [ - { label: 'Add Payment Info', value: 'add_payment_info' }, - { label: 'Add to Cart', value: 'add_to_cart' }, - { label: 'Add to Wishlist', value: 'add_to_wishlist' }, - { label: 'App Install', value: 'app_install' }, - { label: 'App Open', value: 'app_open' }, - { label: 'Checkout', value: 'checkout' }, - { label: 'Contact', value: 'contact' }, - { label: 'Custom', value: 'custom' }, - { label: 'Customize Product', value: 'customize_product' }, - { label: 'Find Location', value: 'find_location' }, - { label: 'Initiate Checkout', value: 'initiate_checkout' }, - { label: 'Lead', value: 'lead' }, - { label: 'Page Visit', value: 'page_visit' }, - { label: 'Schedule', value: 'schedule' }, - { label: 'Search', value: 'search' }, - { label: 'Sign Up', value: 'signup' }, - { label: 'Start Trial', value: 'start_trial' }, - { label: 'Submit Application', value: 'submit_application' }, - { label: 'Subscribe', value: 'subscribe' }, - { label: 'View Category', value: 'view_category' }, - { label: 'View Content', value: 'view_content' }, - { label: 'Watch Video', value: 'watch_video' } + { 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: {