From e2207d3e6112dd5b5439ff8841cdef0bf10e55bf Mon Sep 17 00:00:00 2001 From: Jaehyuk Rhee Date: Thu, 18 Sep 2025 15:47:08 -0400 Subject: [PATCH 1/8] tiktok pixel sdk crm update --- .../src/identify/generated-types.ts | 16 ++ .../tiktok-pixel/src/identify/index.ts | 2 +- .../src/reportWebEvent/constants.ts | 4 + .../fields}/common_fields.ts | 46 ++++ .../src/reportWebEvent/fields/crm_fields.ts | 48 ++++ .../reportWebEvent/fields/travel_fields.ts | 216 +++++++++++++++ .../reportWebEvent/fields/vehicle_fields.ts | 255 ++++++++++++++++++ .../src/reportWebEvent/generated-types.ts | 16 ++ .../tiktok-pixel/src/reportWebEvent/index.ts | 12 +- .../destinations/tiktok-pixel/src/types.ts | 118 +++++--- .../destinations/tiktok-pixel/src/utils.ts | 2 + 11 files changed, 701 insertions(+), 34 deletions(-) create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts rename packages/browser-destinations/destinations/tiktok-pixel/src/{ => reportWebEvent/fields}/common_fields.ts (83%) create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/vehicle_fields.ts create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/generated-types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/generated-types.ts index ac49bf05424..5cef7f60c71 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/generated-types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/generated-types.ts @@ -1,6 +1,10 @@ // Generated file. DO NOT MODIFY IT BY HAND. export interface Payload { + /** + * Include fields for travel or vehicle events. + */ + event_spec_type?: string /** * Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel SDK documentation](https://business-api.tiktok.com/portal/docs?id=1739585696931842) for accepted event names. */ @@ -87,6 +91,14 @@ export interface Payload { */ brand?: string }[] + /** + * Product IDs associated with the event, such as SKUs. Do not populate this field if the 'Contents' field is populated. This field accepts a single string value or an array of string values. + */ + content_ids?: string[] + /** + * Number of items when checkout was initiated. Used with the InitiateCheckout event. + */ + num_items?: number /** * Type of the product item. When the `content_id` in the `Contents` field is specified as a `sku_id`, set this field to `product`. When the `content_id` in the `Contents` field is specified as an `item_group_id`, set this field to `product_group`. */ @@ -107,4 +119,8 @@ export interface Payload { * The text string that was searched for. */ query?: string + /** + * The text string entered by the user for the search. Optionally used with the Search event. + */ + search_string?: string } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts index e73bae13630..b0f81793358 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts @@ -3,7 +3,7 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import { formatPhone, handleArrayInput, formatString, formatAddress } from '../formatter' import { TikTokPixel } from '../types' -import { commonFields } from '../common_fields' +import { commonFields } from '../reportWebEvent/fields/common_fields' // Change from unknown to the partner SDK types const action: BrowserActionDefinition = { diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts new file mode 100644 index 00000000000..e3986781f3e --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts @@ -0,0 +1,4 @@ +export const VEHICLE_FIELDS = 'vehicle_fields' +export const TRAVEL_FIELDS = 'travel_fields' +export const WEB = 'web' +export const CRM = 'crm' diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts similarity index 83% rename from packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts rename to packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts index e4111ed6ef5..9b656325410 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/common_fields.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts @@ -1,6 +1,17 @@ import { InputField } from '@segment/actions-core' +import { VEHICLE_FIELDS, TRAVEL_FIELDS, CRM } from '../constants' export const commonFields: Record = { + event_spec_type: { + label: 'Additional Fields', + type: 'string', + description: 'Include fields for travel or vehicle events.', + choices: [ + { value: CRM, label: 'CRM Fields' }, + { value: TRAVEL_FIELDS, label: 'Travel Fields' }, + { value: VEHICLE_FIELDS, label: 'Vehicle Fields' } + ] + }, event: { label: 'Event Name', type: 'string', @@ -195,6 +206,24 @@ export const commonFields: Record = { } } }, + content_ids: { + label: 'Content IDs', + description: + "Product IDs associated with the event, such as SKUs. Do not populate this field if the 'Contents' field is populated. This field accepts a single string value or an array of string values.", + type: 'string', + multiple: true, + default: { + '@path': '$.properties.content_ids' + } + }, + num_items: { + label: 'Number of Items', + type: 'number', + description: 'Number of items when checkout was initiated. Used with the InitiateCheckout event.', + default: { + '@path': '$.properties.num_items' + } + }, content_type: { label: 'Content Type', description: @@ -238,5 +267,22 @@ export const commonFields: Record = { default: { '@path': '$.properties.query' } + }, + search_string: { + label: 'Search String', + type: 'string', + description: 'The text string entered by the user for the search. Optionally used with the Search event.', + default: { + '@path': '$.properties.search_string' + }, + depends_on: { + conditions: [ + { + fieldKey: 'event', + operator: 'is', + value: 'Search' + } + ] + } } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts new file mode 100644 index 00000000000..b9c1486cda1 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts @@ -0,0 +1,48 @@ +import { InputField } from '@segment/actions-core' +import { CRM } from '../constants' + +export const crm_fields: Record = { + lead_fields: { + label: 'CRM Fields', + type: 'object', + description: 'Fields related to CRM events.', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + properties: { + lead_id: { + label: 'TikTok Lead ID', + description: + 'ID of TikTok leads. Every lead will have its own lead_id when exported from TikTok. This feature is in Beta. Please contact your TikTok representative to inquire regarding availability', + type: 'string' + }, + lead_event_source: { + label: 'TikTok Lead Event Source', + description: + 'Lead source of TikTok leads. Please set this field to the name of your CRM system, such as HubSpot or Salesforce.', + type: 'string' + } + }, + default: { + lead_id: { '@path': '$.properties.lead_id' }, + lead_event_source: { '@path': '$.properties.lead_event_source' } + }, + // required: { + // conditions: [ + // { + // fieldKey: 'event_source', + // operator: 'is', + // value: CRM + // } + // ] + // }, + depends_on: { + conditions: [ + { + fieldKey: 'event_spec_type', + operator: 'is', + value: CRM + } + ] + } + } +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts new file mode 100644 index 00000000000..ed67acd1280 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts @@ -0,0 +1,216 @@ +import { InputField } from '@segment/actions-core' +import { TRAVEL_FIELDS } from '../constants' + +export const travel_fields: InputField = { + label: 'Travel Fields', + type: 'object', + description: 'Fields related to travel events.', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + properties: { + city: { + label: 'Hotel City Location', + type: 'string', + description: 'Hotel city location.' + }, + region: { + label: 'Hotel Region', + type: 'string', + description: 'Hotel region location.' + }, + country: { + label: 'Hotel Country', + type: 'string', + description: 'Hotel country location.' + }, + checkin_date: { + label: 'Hotel Check-in Date', + type: 'string', + description: 'Hotel check-in date.' + }, + checkout_date: { + label: 'Hotel Check-out Date', + type: 'string', + description: 'Hotel check-out date.' + }, + num_adults: { + label: 'Number of Adults', + type: 'number', + description: 'Number of adults.' + }, + num_children: { + label: 'Number of Children', + type: 'number', + description: 'Number of children.' + }, + num_infants: { + label: 'Number of Infants', + type: 'number', + description: 'Number of infants flying.' + }, + suggested_hotels: { + label: 'Suggested Hotels', + description: 'Suggested hotels. This can be a single string value or an array of string values.', + type: 'string', + multiple: true + }, + departing_departure_date: { + label: 'Departure Date', + type: 'string', + description: + 'Date of flight departure. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD' + }, + returning_departure_date: { + label: 'Arrival Date', + type: 'string', + description: + 'Date of return flight. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD' + }, + origin_airport: { + label: 'Origin Airport', + type: 'string', + description: 'Origin airport.' + }, + destination_airport: { + label: 'Destination Airport', + type: 'string', + description: 'Destination airport.' + }, + destination_ids: { + label: 'Destination IDs', + description: + 'If a client has a destination catalog, the client can associate one or more destinations in the catalog with a specific flight event. For instance, link a particular route to a nearby museum and a nearby beach, both of which are destinations in the catalog. This field accepts a single string value or an array of string values.', + type: 'string', + multiple: true + }, + departing_arrival_date: { + label: 'Departing Arrival Date', + type: 'string', + description: + 'The date and time for arrival at the destination of the outbound journey. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD' + }, + returning_arrival_date: { + label: 'Returning Arrival Date', + type: 'string', + description: + 'The date and time when the return journey is completed. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD' + }, + travel_class: { + label: 'Flight Ticket Class', + type: 'string', + description: 'Class of the flight ticket, must be: "eco", "prem", "bus", "first".', + choices: [ + { value: 'eco', label: 'Economy' }, + { value: 'prem', label: 'Premium' }, + { value: 'bus', label: 'Bus' }, + { value: 'first', label: 'First' } + ] + }, + user_score: { + label: 'User Score', + type: 'number', + description: 'Represents the relative value of this potential customer to advertiser.' + }, + preferred_num_stops: { + label: 'Preferred Number of Stops', + type: 'integer', + description: 'The preferred number of stops the user is looking for. 0 for direct flight.' + }, + travel_start: { + label: 'Start Date of the Trip', + type: 'string', + description: + "The start date of user's trip. Accept date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD." + }, + travel_end: { + label: 'End Date of the Trip', + type: 'string', + description: + "The end date of user's trip. Accept date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD." + }, + suggested_destinations: { + label: 'Suggested Destination IDs', + description: + 'A list of IDs representing destination suggestions for this user. This parameter is not applicable for the Search event. This field accepts a single string value or an array of string values.', + type: 'string', + multiple: true + } + }, + default: { + city: { + '@path': '$.properties.city' + }, + region: { + '@path': '$.properties.region' + }, + country: { + '@path': '$.properties.country' + }, + checkin_date: { + '@path': '$.properties.checkin_date' + }, + checkout_date: { + '@path': '$.properties.checkout_date' + }, + num_adults: { + '@path': '$.properties.num_adults' + }, + num_children: { + '@path': '$.properties.num_children' + }, + num_infants: { + '@path': '$.properties.num_infants' + }, + suggested_hotels: { + '@path': '$.properties.suggested_hotels' // Confirmed this can be a single string or an array of strings + }, + departing_departure_date: { + '@path': '$.properties.departing_departure_date' + }, + returning_departure_date: { + '@path': '$.properties.returning_departure_date' + }, + origin_airport: { + '@path': '$.properties.origin_airport' + }, + destination_airiport: { + '@path': '$.properties.destination_airiport' + }, + destination_ids: { + '@path': '$.properties.destination_ids' // Confirmed this can be a single string or an array of strings + }, + departing_arrival_date: { + '@path': '$.properties.departing_arrival_date' + }, + returning_arrival_date: { + '@path': '$.properties.returning_arrival_date' + }, + travel_class: { + '@path': '$.properties.travel_class' + }, + user_score: { + '@path': '$.properties.user_score' + }, + preferred_num_stops: { + '@path': '$.properties.preferred_num_stops' + }, + travel_start: { + '@path': '$.properties.travel_start' + }, + travel_end: { + '@path': '$.properties.travel_end' + }, + suggested_destinations: { + '@path': '$.properties.suggested_destinations' // Confirmed this can be a single string or an array of strings + } + }, + depends_on: { + conditions: [ + { + fieldKey: 'event_spec_type', + operator: 'is', + value: TRAVEL_FIELDS + } + ] + } +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/vehicle_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/vehicle_fields.ts new file mode 100644 index 00000000000..7d916a84781 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/vehicle_fields.ts @@ -0,0 +1,255 @@ +import { InputField } from '@segment/actions-core' +import { VEHICLE_FIELDS } from '../constants' + +export const vehicle_fields: InputField = { + label: 'Vehicle Fields', + type: 'object', + description: 'Fields related to vehicle events.', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + properties: { + postal_code: { + label: 'Postal Code', + type: 'string', + description: 'Postal code for the vehicle location.' + }, + make: { + label: 'Make of the Vehicle', + type: 'string', + description: 'Vehicle make/brand/manufacturer.' + }, + model: { + label: 'Model of the Vehicle', + type: 'string', + description: 'Vehicle model.' + }, + year: { + label: 'Year of the Vehicle', + type: 'number', + description: 'Year the vehicle was laucned in yyyy format.' + }, + state_of_vehicle: { + label: 'State of the Vehicle', + type: 'string', + description: 'Vehicle status.', + choices: [ + { value: 'New', label: 'New' }, + { value: 'Used', label: 'Used' }, + { value: 'CPO', label: 'CPO' } + ] + }, + mileage_value: { + label: 'Mileage Value', + type: 'number', + description: 'Vehicle mileage (in km or miles). Zero (0) for new vehicle.' + }, + mileage_unit: { + label: 'Mileage Unit', + type: 'string', + description: 'Mileage unites in miles (MI) or kilometers (KM).', + choices: [ + { value: 'MI', label: 'Miles' }, + { value: 'KM', label: 'Kilometers' } + ] + }, + exterior_color: { + label: 'Exterior Color of the Vehicle', + type: 'string', + description: 'Vehicle exterior color.' + }, + transmission: { + label: 'Transmission Type of the Vehicle', + type: 'string', + description: 'Vehicle transmission type.', + choices: [ + { value: 'Automatic', label: 'Automatic' }, + { value: 'Manual', label: 'Manual' }, + { value: 'Other', label: 'Other' } + ] + }, + body_style: { + label: 'Body Type of the Vehicle', + type: 'string', + description: 'Vehicle body type.', + choices: [ + { value: 'Convertible', label: 'Convertible' }, + { value: 'Coupe', label: 'Coupe' }, + { value: 'Hatchback', label: 'Hatchback' }, + { value: 'Minivan', label: 'Minivan' }, + { value: 'Truck', label: 'Truck' }, + { value: 'SUV', label: 'SUV' }, + { value: 'Sedan', label: 'Sedan' }, + { value: 'Van', label: 'Van' }, + { value: 'Wagon', label: 'Wagon' }, + { value: 'Crossover', label: 'Crossover' }, + { value: 'Other', label: 'Other' } + ] + }, + fuel_type: { + label: 'Fuel Type of the Vehicle', + type: 'string', + description: 'Vehicle fuel type.', + choices: [ + { value: 'Diesel', label: 'Diesel' }, + { value: 'Electric', label: 'Electric' }, + { value: 'Flex', label: 'Flex' }, + { value: 'Gasoline', label: 'Gasoline' }, + { value: 'Hybrid', label: 'Hybrid' }, + { value: 'Other', label: 'Other' } + ] + }, + drivetrain: { + label: 'Drivetrain of the Vehicle', + type: 'string', + description: 'Vehicle drivetrain.', + choices: [ + { value: 'AWD', label: 'AWD' }, + { value: 'FOUR_WD', label: 'Four WD' }, + { value: 'FWD', label: 'FWD' }, + { value: 'RWD', label: 'RWD' }, + { value: 'TWO_WD', label: 'Two WD' }, + { value: 'Other', label: 'Other' } + ] + }, + preferred_price_range_min: { + label: 'Minimum Preferred Price', + type: 'number', + description: 'Minimum preferred price of the vehicle.' + }, + preferred_price_range_max: { + label: 'Maximum Preferred Price', + type: 'number', + description: 'Maximum preferred price of the vehicle.' + }, + trim: { + label: 'Trim of the Vehicle', + type: 'string', + description: 'Vehicle trim.' + }, + vin: { + label: 'VIN of the Vehicle', + type: 'string', + description: 'Vehicle identification number. Maximum characters: 17.' + }, + interior_color: { + label: 'Interior Color of the Vehicle', + type: 'string', + description: 'Vehicle interior color.' + }, + condition_of_vehicle: { + label: 'Condition of the Vehicle', + type: 'string', + description: 'Vehicle drivetrain.', + choices: [ + { value: 'Excellent', label: 'Excellent' }, + { value: 'Good', label: 'Good' }, + { value: 'Fair', label: 'Fair' }, + { value: 'Poor', label: 'Poor' }, + { value: 'Other', label: 'Other' } + ] + }, + viewcontent_type: { + label: 'Soft Lead Landing Page', + type: 'string', + description: 'Optional for ViewContent. Use viewcontent_type to differentiate between soft lead landing pages.', + depends_on: { + match: 'any', + conditions: [{ fieldKey: 'event', operator: 'is', value: 'ViewContent' }] + } + }, + search_type: { + label: 'Other Search Page', + type: 'string', + description: + 'Optional for Search. Use search_type to differentiate other user searches (such as dealer lookup) from inventory search.', + depends_on: { + match: 'any', + conditions: [{ fieldKey: 'event', operator: 'is', value: 'Search' }] + } + }, + registration_type: { + label: 'Other Registration Page', + type: 'string', + description: + 'Optional for CompleteRegistration. Use registration_type to differentiate between different types of customer registration on websites.', + depends_on: { + match: 'any', + conditions: [{ fieldKey: 'event', operator: 'is', value: 'CompleteRegistration' }] + } + } + }, + default: { + postal_code: { + '@path': '$.properties.postal_code' + }, + make: { + '@path': '$.properties.make' + }, + model: { + '@path': '$.properties.model' + }, + year: { + '@path': '$.properties.year' + }, + state_of_vehicle: { + '@path': '$.properties.state_of_vehicle' + }, + mileage_value: { + '@path': '$.properties.mileage_value' + }, + mileage_unit: { + '@path': '$.properties.mileage_unit' + }, + exterior_color: { + '@path': '$.properties.exterior_color' + }, + transmission: { + '@path': '$.properties.transmission' + }, + body_style: { + '@path': '$.properties.body_style' + }, + fuel_type: { + '@path': '$.properties.fuel_type' + }, + drivetrain: { + '@path': '$.properties.drive_train' + }, + preferred_price_range_min: { + '@path': '$.properties.preferred_price_range_min' + }, + preferred_price_range_max: { + '@path': '$.properties.preferred_price_range_max' + }, + trim: { + '@path': '$.properties.trim' + }, + vin: { + '@path': '$.properties.vin' + }, + interior_color: { + '@path': '$.properties.interior_color' + }, + condition_of_vehicle: { + '@path': '$.properties.condition_of_vehicle' + }, + viewcontent_type: { + '@path': '$.properties.viewcontent_type' + }, + search_type: { + '@path': '$.properties.search_type' + }, + registration_type: { + '@path': '$.properties.registration_type' + } + }, + depends_on: { + conditions: [ + { + fieldKey: 'event_spec_type', + operator: 'is', + value: VEHICLE_FIELDS + } + ] + } +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts index ac49bf05424..5cef7f60c71 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts @@ -1,6 +1,10 @@ // Generated file. DO NOT MODIFY IT BY HAND. export interface Payload { + /** + * Include fields for travel or vehicle events. + */ + event_spec_type?: string /** * Conversion event name. Please refer to the "Supported Web Events" section on in TikTok’s [Pixel SDK documentation](https://business-api.tiktok.com/portal/docs?id=1739585696931842) for accepted event names. */ @@ -87,6 +91,14 @@ export interface Payload { */ brand?: string }[] + /** + * Product IDs associated with the event, such as SKUs. Do not populate this field if the 'Contents' field is populated. This field accepts a single string value or an array of string values. + */ + content_ids?: string[] + /** + * Number of items when checkout was initiated. Used with the InitiateCheckout event. + */ + num_items?: number /** * Type of the product item. When the `content_id` in the `Contents` field is specified as a `sku_id`, set this field to `product`. When the `content_id` in the `Contents` field is specified as an `item_group_id`, set this field to `product_group`. */ @@ -107,4 +119,8 @@ export interface Payload { * The text string that was searched for. */ query?: string + /** + * The text string entered by the user for the search. Optionally used with the Search event. + */ + search_string?: string } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts index c5667254ceb..35bc0a0c25d 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts @@ -3,7 +3,10 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import { formatPhone, handleArrayInput, formatString, formatAddress } from '../formatter' import { TikTokPixel } from '../types' -import { commonFields } from '../common_fields' +import { commonFields } from './fields/common_fields' +import { crm_fields } from './fields/crm_fields' +import { travel_fields } from './fields/travel_fields' +import { vehicle_fields } from './fields/vehicle_fields' const action: BrowserActionDefinition = { title: 'Report Web Event', @@ -12,11 +15,14 @@ const action: BrowserActionDefinition = { platform: 'web', defaultSubscription: 'type = "track"', fields: { - ...commonFields + ...commonFields, + ...crm_fields, + vehicle_fields, + travel_fields }, perform: (ttq, { payload, settings }) => { if (payload.email || payload.phone_number || payload.external_id) { - ttq.identify({ + ttq.instance(settings.pixelCode).identify({ email: handleArrayInput(payload.email), phone_number: formatPhone(handleArrayInput(payload.phone_number)), external_id: handleArrayInput(payload.external_id), diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts index 93042f663dd..1653a1e32d2 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts @@ -11,17 +11,7 @@ export interface TikTokPixel { state, country, zip_code - }: { - email: string | undefined - phone_number: string | undefined - external_id: string | undefined - first_name: string | undefined - last_name: string | undefined - city: string | undefined - state: string | undefined - country: string | undefined - zip_code: string | undefined - }) => void + }: TTUser) => void track: ( event: string, { @@ -33,25 +23,7 @@ export interface TikTokPixel { query, order_id, shop_id - }: { - contents: - | { - price?: number | undefined - quantity?: number | undefined - content_category?: string | undefined - content_id?: string | undefined - content_name?: string | undefined - brand?: string | undefined - }[] - | [] - content_type: string | undefined - currency: string | undefined - value: number | undefined - description: string | undefined - query: string | undefined - order_id: string | undefined - shop_id: string | undefined - }, + }: TTBaseProps & TTTravelProps & TTAutoProps, { event_id }: { @@ -59,3 +31,89 @@ export interface TikTokPixel { } ) => void } + +export interface TTUser { + external_id: string + phone_number: string | undefined + email: string + locale?: string + first_name?: string + last_name?: string + city?: string + state?: string + country?: string + zip_code?: string +} + +export interface TTBaseProps { + contents: TTContentItem[] + content_type?: string + currency?: string + value?: number + query?: string + description?: string + order_id?: string + shop_id?: string + content_ids?: string[] + num_items?: number + search_string?: string +} + +export interface TTTravelProps { + city?: string + region?: string + country?: string + checkin_date?: string + checkout_date?: string + num_adults?: number + num_children?: number + num_infants?: number + suggested_hotels?: string[] + departing_departure_date?: string + returning_departure_date?: string + origin_airport?: string + destination_airiport?: string + destination_ids?: string[] + departing_arrival_date?: string + returning_arrival_date?: string + travel_class?: string + user_score?: number + preferred_num_stops?: number + travel_start?: string + travel_end?: string + suggested_destinations?: string[] +} + +export interface TTAutoProps { + postal_code?: string + make?: string + model?: string + year?: number + state_of_vehicle?: string + mileage?: { + value?: number + unit?: string + } + exterior_color?: string + transmission?: string + body_style?: string + fuel_type?: string + drivetrain?: string + preferred_price_range?: number[] + trim?: string + vin?: string + interior_color?: string + condition_of_vehicle?: string + viewcontent_type?: string + search_type?: string + registration_type?: string +} + +interface TTContentItem { + price?: number + quantity?: number + content_category?: string + content_id?: string + content_name?: string + brand?: string +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts new file mode 100644 index 00000000000..7c60225d791 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts @@ -0,0 +1,2 @@ + +// function getUser(payload: Payload) \ No newline at end of file From d621d7ef15fd5ad490445221aceb70d09bfaa103 Mon Sep 17 00:00:00 2001 From: Jaehyuk Rhee Date: Thu, 25 Sep 2025 13:56:20 -0400 Subject: [PATCH 2/8] add new params for pixel sdk --- .../tiktok-pixel/src/identify/index.ts | 16 +- .../src/reportWebEvent/constants.ts | 2 - .../reportWebEvent/fields/common_fields.ts | 3 +- .../src/reportWebEvent/fields/crm_fields.ts | 48 ----- .../src/reportWebEvent/generated-types.ts | 182 ++++++++++++++++++ .../tiktok-pixel/src/reportWebEvent/index.ts | 36 +--- .../tiktok-pixel/src/reportWebEvent/utils.ts | 167 ++++++++++++++++ .../destinations/tiktok-pixel/src/types.ts | 48 ++++- .../destinations/tiktok-pixel/src/utils.ts | 21 +- 9 files changed, 425 insertions(+), 98 deletions(-) delete mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts create mode 100644 packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts index b0f81793358..525e1ff5ea0 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts @@ -1,7 +1,7 @@ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { formatPhone, handleArrayInput, formatString, formatAddress } from '../formatter' +import { getUserObject } from '../utils' import { TikTokPixel } from '../types' import { commonFields } from '../reportWebEvent/fields/common_fields' @@ -40,19 +40,9 @@ const action: BrowserActionDefinition = { } } }, - perform: (ttq, { payload }) => { + perform: (ttq, { payload, settings }) => { if (payload.email || payload.phone_number || payload.external_id) { - ttq.identify({ - email: handleArrayInput(payload.email), - phone_number: formatPhone(handleArrayInput(payload.phone_number)), - external_id: handleArrayInput(payload.external_id), - first_name: formatString(payload.first_name), - last_name: formatString(payload.last_name), - city: formatAddress(payload.address?.city), - state: formatAddress(payload.address?.state), - country: formatAddress(payload.address?.country), - zip_code: formatString(payload.address?.zip_code) - }) + ttq.instance(settings.pixelCode).identify(getUserObject(payload)) } } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts index e3986781f3e..9c10e826fa0 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/constants.ts @@ -1,4 +1,2 @@ export const VEHICLE_FIELDS = 'vehicle_fields' export const TRAVEL_FIELDS = 'travel_fields' -export const WEB = 'web' -export const CRM = 'crm' diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts index 9b656325410..772e9c37743 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts @@ -1,5 +1,5 @@ import { InputField } from '@segment/actions-core' -import { VEHICLE_FIELDS, TRAVEL_FIELDS, CRM } from '../constants' +import { VEHICLE_FIELDS, TRAVEL_FIELDS } from '../constants' export const commonFields: Record = { event_spec_type: { @@ -7,7 +7,6 @@ export const commonFields: Record = { type: 'string', description: 'Include fields for travel or vehicle events.', choices: [ - { value: CRM, label: 'CRM Fields' }, { value: TRAVEL_FIELDS, label: 'Travel Fields' }, { value: VEHICLE_FIELDS, label: 'Vehicle Fields' } ] diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts deleted file mode 100644 index b9c1486cda1..00000000000 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/crm_fields.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { InputField } from '@segment/actions-core' -import { CRM } from '../constants' - -export const crm_fields: Record = { - lead_fields: { - label: 'CRM Fields', - type: 'object', - description: 'Fields related to CRM events.', - additionalProperties: false, - defaultObjectUI: 'keyvalue', - properties: { - lead_id: { - label: 'TikTok Lead ID', - description: - 'ID of TikTok leads. Every lead will have its own lead_id when exported from TikTok. This feature is in Beta. Please contact your TikTok representative to inquire regarding availability', - type: 'string' - }, - lead_event_source: { - label: 'TikTok Lead Event Source', - description: - 'Lead source of TikTok leads. Please set this field to the name of your CRM system, such as HubSpot or Salesforce.', - type: 'string' - } - }, - default: { - lead_id: { '@path': '$.properties.lead_id' }, - lead_event_source: { '@path': '$.properties.lead_event_source' } - }, - // required: { - // conditions: [ - // { - // fieldKey: 'event_source', - // operator: 'is', - // value: CRM - // } - // ] - // }, - depends_on: { - conditions: [ - { - fieldKey: 'event_spec_type', - operator: 'is', - value: CRM - } - ] - } - } -} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts index 5cef7f60c71..323cd7cdd92 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/generated-types.ts @@ -123,4 +123,186 @@ export interface Payload { * The text string entered by the user for the search. Optionally used with the Search event. */ search_string?: string + /** + * Fields related to vehicle events. + */ + vehicle_fields?: { + /** + * Postal code for the vehicle location. + */ + postal_code?: string + /** + * Vehicle make/brand/manufacturer. + */ + make?: string + /** + * Vehicle model. + */ + model?: string + /** + * Year the vehicle was laucned in yyyy format. + */ + year?: number + /** + * Vehicle status. + */ + state_of_vehicle?: string + /** + * Vehicle mileage (in km or miles). Zero (0) for new vehicle. + */ + mileage_value?: number + /** + * Mileage unites in miles (MI) or kilometers (KM). + */ + mileage_unit?: string + /** + * Vehicle exterior color. + */ + exterior_color?: string + /** + * Vehicle transmission type. + */ + transmission?: string + /** + * Vehicle body type. + */ + body_style?: string + /** + * Vehicle fuel type. + */ + fuel_type?: string + /** + * Vehicle drivetrain. + */ + drivetrain?: string + /** + * Minimum preferred price of the vehicle. + */ + preferred_price_range_min?: number + /** + * Maximum preferred price of the vehicle. + */ + preferred_price_range_max?: number + /** + * Vehicle trim. + */ + trim?: string + /** + * Vehicle identification number. Maximum characters: 17. + */ + vin?: string + /** + * Vehicle interior color. + */ + interior_color?: string + /** + * Vehicle drivetrain. + */ + condition_of_vehicle?: string + /** + * Optional for ViewContent. Use viewcontent_type to differentiate between soft lead landing pages. + */ + viewcontent_type?: string + /** + * Optional for Search. Use search_type to differentiate other user searches (such as dealer lookup) from inventory search. + */ + search_type?: string + /** + * Optional for CompleteRegistration. Use registration_type to differentiate between different types of customer registration on websites. + */ + registration_type?: string + } + /** + * Fields related to travel events. + */ + travel_fields?: { + /** + * Hotel city location. + */ + city?: string + /** + * Hotel region location. + */ + region?: string + /** + * Hotel country location. + */ + country?: string + /** + * Hotel check-in date. + */ + checkin_date?: string + /** + * Hotel check-out date. + */ + checkout_date?: string + /** + * Number of adults. + */ + num_adults?: number + /** + * Number of children. + */ + num_children?: number + /** + * Number of infants flying. + */ + num_infants?: number + /** + * Suggested hotels. This can be a single string value or an array of string values. + */ + suggested_hotels?: string[] + /** + * Date of flight departure. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD + */ + departing_departure_date?: string + /** + * Date of return flight. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD + */ + returning_departure_date?: string + /** + * Origin airport. + */ + origin_airport?: string + /** + * Destination airport. + */ + destination_airport?: string + /** + * If a client has a destination catalog, the client can associate one or more destinations in the catalog with a specific flight event. For instance, link a particular route to a nearby museum and a nearby beach, both of which are destinations in the catalog. This field accepts a single string value or an array of string values. + */ + destination_ids?: string[] + /** + * The date and time for arrival at the destination of the outbound journey. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD + */ + departing_arrival_date?: string + /** + * The date and time when the return journey is completed. Accepted date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD + */ + returning_arrival_date?: string + /** + * Class of the flight ticket, must be: "eco", "prem", "bus", "first". + */ + travel_class?: string + /** + * Represents the relative value of this potential customer to advertiser. + */ + user_score?: number + /** + * The preferred number of stops the user is looking for. 0 for direct flight. + */ + preferred_num_stops?: number + /** + * The start date of user's trip. Accept date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD. + */ + travel_start?: string + /** + * The end date of user's trip. Accept date formats: YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD, and YYYY-MM-DDThh:mm:ssTZD. + */ + travel_end?: string + /** + * A list of IDs representing destination suggestions for this user. This parameter is not applicable for the Search event. This field accepts a single string value or an array of string values. + */ + suggested_destinations?: string[] + } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts index 35bc0a0c25d..a785fd6d4a4 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts @@ -1,10 +1,10 @@ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { formatPhone, handleArrayInput, formatString, formatAddress } from '../formatter' +import { getUserObject } from '../utils' +import { getProp } from './utils' import { TikTokPixel } from '../types' import { commonFields } from './fields/common_fields' -import { crm_fields } from './fields/crm_fields' import { travel_fields } from './fields/travel_fields' import { vehicle_fields } from './fields/vehicle_fields' @@ -16,41 +16,17 @@ const action: BrowserActionDefinition = { defaultSubscription: 'type = "track"', fields: { ...commonFields, - ...crm_fields, vehicle_fields, travel_fields }, perform: (ttq, { payload, settings }) => { if (payload.email || payload.phone_number || payload.external_id) { - ttq.instance(settings.pixelCode).identify({ - email: handleArrayInput(payload.email), - phone_number: formatPhone(handleArrayInput(payload.phone_number)), - external_id: handleArrayInput(payload.external_id), - first_name: formatString(payload.first_name), - last_name: formatString(payload.last_name), - city: formatAddress(payload.address?.city), - state: formatAddress(payload.address?.state), - country: formatAddress(payload.address?.country), - zip_code: formatString(payload.address?.zip_code) - }) + ttq.instance(settings.pixelCode).identify(getUserObject(payload)) } - ttq.instance(settings.pixelCode).track( - payload.event, - { - contents: payload.contents ? payload.contents : [], - content_type: payload.content_type ? payload.content_type : undefined, - currency: payload.currency ? payload.currency : 'USD', - value: payload.value || payload.value === 0 ? payload.value : undefined, - query: payload.query ? payload.query : undefined, - description: payload.description ? payload.description : undefined, - order_id: payload.order_id ? payload.order_id : undefined, - shop_id: payload.shop_id ? payload.shop_id : undefined - }, - { - event_id: payload.event_id ? payload.event_id : '' - } - ) + ttq.instance(settings.pixelCode).track(payload.event, getProp(payload), { + event_id: payload.event_id ? payload.event_id : '' + }) } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts new file mode 100644 index 00000000000..c7d5ef9a9b9 --- /dev/null +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts @@ -0,0 +1,167 @@ +import { Payload } from './generated-types' +import { TTBaseProps, TTTravelProps, TTAutoProps } from '../types' +import { TRAVEL_FIELDS, VEHICLE_FIELDS } from './constants' + +export function getProp(payload: Payload): TTBaseProps & TTAutoProps & TTTravelProps { + const { event_spec_type } = payload + + return { + ...buildBaseProp(payload), + ...(event_spec_type === TRAVEL_FIELDS ? buildTravelProp(payload) : {}), + ...(event_spec_type === VEHICLE_FIELDS ? buildAutoProp(payload) : {}) + } +} + +function buildBaseProp(payload: Payload): TTBaseProps { + const { + content_type, + currency, + value, + query, + description, + order_id, + shop_id, + content_ids, + num_items, + search_string, + contents + } = payload + + const requestProperties: TTBaseProps = { + contents: contents + ? contents.map(({ price, quantity, content_category, content_id, content_name, brand }) => ({ + price: price ?? undefined, + quantity: quantity ?? undefined, + content_category: content_category ?? undefined, + content_id: content_id ?? undefined, + content_name: content_name ?? undefined, + brand: brand ?? undefined + })) + : [], + ...(content_type !== undefined && { content_type }), + ...(currency !== undefined && { currency }), + ...(value !== undefined && { value }), + ...(query !== undefined && { query }), + ...(description !== undefined && { description }), + ...(order_id !== undefined && { order_id }), + ...(shop_id !== undefined && { shop_id }), + ...(content_ids !== undefined && { content_ids }), + ...(num_items !== undefined && { num_items }), + ...(search_string !== undefined && { search_string }) + } + + return requestProperties +} + +function buildTravelProp(payload: Payload): TTTravelProps { + const { + city, + region, + country, + checkin_date, + checkout_date, + num_adults, + num_children, + num_infants, + suggested_hotels, + departing_departure_date, + returning_departure_date, + origin_airport, + destination_airport, + destination_ids, + departing_arrival_date, + returning_arrival_date, + travel_class, + user_score, + preferred_num_stops, + travel_start, + travel_end, + suggested_destinations + } = payload?.travel_fields ?? {} + + const requestProperties: TTTravelProps = { + ...(city !== undefined && { city }), + ...(region !== undefined && { region }), + ...(country !== undefined && { country }), + ...(checkin_date !== undefined && { checkin_date }), + ...(checkout_date !== undefined && { checkout_date }), + ...(num_adults !== undefined && { num_adults }), + ...(num_children !== undefined && { num_children }), + ...(num_infants !== undefined && { num_infants }), + ...(suggested_hotels !== undefined && { suggested_hotels }), + ...(departing_departure_date !== undefined && { departing_departure_date }), + ...(returning_departure_date !== undefined && { returning_departure_date }), + ...(origin_airport !== undefined && { origin_airport }), + ...(destination_airport !== undefined && { destination_airport }), + ...(destination_ids !== undefined && { destination_ids }), + ...(departing_arrival_date !== undefined && { departing_arrival_date }), + ...(returning_arrival_date !== undefined && { returning_arrival_date }), + ...(travel_class !== undefined && { travel_class }), + ...(user_score !== undefined && { user_score }), + ...(preferred_num_stops !== undefined && { preferred_num_stops }), + ...(travel_start !== undefined && { travel_start }), + ...(travel_end !== undefined && { travel_end }), + ...(suggested_destinations !== undefined && { suggested_destinations }) + } + + return requestProperties +} + +function buildAutoProp(payload: Payload): TTAutoProps { + const { + postal_code, + make, + model, + year, + state_of_vehicle, + mileage_unit, + mileage_value, + exterior_color, + transmission, + body_style, + fuel_type, + drivetrain, + preferred_price_range_min, + preferred_price_range_max, + trim, + vin, + interior_color, + condition_of_vehicle, + viewcontent_type, + search_type, + registration_type + } = payload?.vehicle_fields ?? {} + + const requestProperties: TTAutoProps = { + ...(postal_code !== undefined && { postal_code }), + ...(make !== undefined && { make }), + ...(model !== undefined && { model }), + ...(year !== undefined && { year }), + ...(state_of_vehicle !== undefined && { state_of_vehicle }), + ...(exterior_color !== undefined && { exterior_color }), + ...(transmission !== undefined && { transmission }), + ...(body_style !== undefined && { body_style }), + ...(fuel_type !== undefined && { fuel_type }), + ...(drivetrain !== undefined && { drivetrain }), + ...(trim !== undefined && { trim }), + ...(vin !== undefined && { vin }), + ...(interior_color !== undefined && { interior_color }), + ...(condition_of_vehicle !== undefined && { condition_of_vehicle }), + ...(viewcontent_type !== undefined && { viewcontent_type }), + ...(search_type !== undefined && { search_type }), + ...(registration_type !== undefined && { registration_type }), + ...(mileage_unit !== undefined && + typeof mileage_value === 'number' && { + mileage: { + unit: mileage_unit, + value: mileage_value + } + }), + ...(typeof preferred_price_range_min === 'number' && + typeof preferred_price_range_max === 'number' && { + preferred_price_range: [preferred_price_range_min, preferred_price_range_max] + }) + } + + return requestProperties +} diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts index 1653a1e32d2..7b3c5419dd1 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts @@ -19,10 +19,54 @@ export interface TikTokPixel { content_type, currency, value, - description, query, + description, order_id, - shop_id + shop_id, + content_ids, + num_items, + search_string, + city, + region, + country, + checkin_date, + checkout_date, + num_adults, + num_children, + num_infants, + suggested_hotels, + departing_departure_date, + returning_departure_date, + origin_airport, + destination_airiport, + destination_ids, + departing_arrival_date, + returning_arrival_date, + travel_class, + user_score, + preferred_num_stops, + travel_start, + travel_end, + suggested_destinations, + postal_code, + make, + model, + year, + state_of_vehicle, + mileage, + exterior_color, + transmission, + body_style, + fuel_type, + drivetrain, + preferred_price_range, + trim, + vin, + interior_color, + condition_of_vehicle, + viewcontent_type, + search_type, + registration_type }: TTBaseProps & TTTravelProps & TTAutoProps, { event_id diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts index 7c60225d791..7ce7a876f8b 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts @@ -1,2 +1,21 @@ +import { Payload } from './reportWebEvent/generated-types' +import { formatPhone, handleArrayInput, formatString, formatAddress } from './formatter' +import { TTUser } from './types' -// function getUser(payload: Payload) \ No newline at end of file +export function getUserObject(payload: Payload): TTUser { + return buildUserObject(payload) +} + +function buildUserObject(payload: Payload): TTUser { + return { + email: handleArrayInput(payload.email), + phone_number: formatPhone(handleArrayInput(payload.phone_number)), + external_id: handleArrayInput(payload.external_id), + first_name: formatString(payload.first_name), + last_name: formatString(payload.last_name), + city: formatAddress(payload.address?.city), + state: formatAddress(payload.address?.state), + country: formatAddress(payload.address?.country), + zip_code: formatString(payload.address?.zip_code) + } +} From 95e6736edeb27e5522aa18e93affd96d22c0fca6 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 2 Oct 2025 13:46:06 +0100 Subject: [PATCH 3/8] reviewer PR --- .../tiktok-pixel/src/identify/index.ts | 4 +- .../reportWebEvent/fields/travel_fields.ts | 4 +- .../tiktok-pixel/src/reportWebEvent/index.ts | 16 ++-- .../tiktok-pixel/src/reportWebEvent/utils.ts | 14 ++-- .../destinations/tiktok-pixel/src/types.ts | 73 +------------------ .../destinations/tiktok-pixel/src/utils.ts | 8 +- .../reportWebEvent/fields/travel_fields.ts | 4 +- 7 files changed, 28 insertions(+), 95 deletions(-) diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts index 525e1ff5ea0..2767a001a36 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts @@ -1,7 +1,7 @@ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { getUserObject } from '../utils' +import { getUser } from '../utils' import { TikTokPixel } from '../types' import { commonFields } from '../reportWebEvent/fields/common_fields' @@ -42,7 +42,7 @@ const action: BrowserActionDefinition = { }, perform: (ttq, { payload, settings }) => { if (payload.email || payload.phone_number || payload.external_id) { - ttq.instance(settings.pixelCode).identify(getUserObject(payload)) + ttq.instance(settings.pixelCode).identify(getUser(payload)) } } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts index ed67acd1280..a0dbdd47489 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/travel_fields.ts @@ -173,8 +173,8 @@ export const travel_fields: InputField = { origin_airport: { '@path': '$.properties.origin_airport' }, - destination_airiport: { - '@path': '$.properties.destination_airiport' + destination_airport: { + '@path': '$.properties.destination_airport' }, destination_ids: { '@path': '$.properties.destination_ids' // Confirmed this can be a single string or an array of strings diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts index a785fd6d4a4..f464877ccc4 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts @@ -1,8 +1,8 @@ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { getUserObject } from '../utils' -import { getProp } from './utils' +import { getUser } from '../utils' +import { getAllProperties } from './utils' import { TikTokPixel } from '../types' import { commonFields } from './fields/common_fields' import { travel_fields } from './fields/travel_fields' @@ -21,12 +21,16 @@ const action: BrowserActionDefinition = { }, perform: (ttq, { payload, settings }) => { if (payload.email || payload.phone_number || payload.external_id) { - ttq.instance(settings.pixelCode).identify(getUserObject(payload)) + ttq.instance(settings.pixelCode).identify(getUser(payload)) } - ttq.instance(settings.pixelCode).track(payload.event, getProp(payload), { - event_id: payload.event_id ? payload.event_id : '' - }) + ttq.instance(settings.pixelCode).track( + payload.event, + getAllProperties(payload), + { + event_id: payload.event_id ? payload.event_id : '' + } + ) } } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts index c7d5ef9a9b9..df4a41530c1 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts @@ -2,17 +2,17 @@ import { Payload } from './generated-types' import { TTBaseProps, TTTravelProps, TTAutoProps } from '../types' import { TRAVEL_FIELDS, VEHICLE_FIELDS } from './constants' -export function getProp(payload: Payload): TTBaseProps & TTAutoProps & TTTravelProps { +export function getAllProperties(payload: Payload): TTBaseProps & TTAutoProps & TTTravelProps { const { event_spec_type } = payload return { - ...buildBaseProp(payload), - ...(event_spec_type === TRAVEL_FIELDS ? buildTravelProp(payload) : {}), - ...(event_spec_type === VEHICLE_FIELDS ? buildAutoProp(payload) : {}) + ...getProps(payload), + ...(event_spec_type === TRAVEL_FIELDS ? getTravelProps(payload) : {}), + ...(event_spec_type === VEHICLE_FIELDS ? getAutoProp(payload) : {}) } } -function buildBaseProp(payload: Payload): TTBaseProps { +function getProps(payload: Payload): TTBaseProps { const { content_type, currency, @@ -53,7 +53,7 @@ function buildBaseProp(payload: Payload): TTBaseProps { return requestProperties } -function buildTravelProp(payload: Payload): TTTravelProps { +function getTravelProps(payload: Payload): TTTravelProps { const { city, region, @@ -107,7 +107,7 @@ function buildTravelProp(payload: Payload): TTTravelProps { return requestProperties } -function buildAutoProp(payload: Payload): TTAutoProps { +function getAutoProp(payload: Payload): TTAutoProps { const { postal_code, make, diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts index 7b3c5419dd1..6dc176ef900 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/types.ts @@ -1,78 +1,11 @@ export interface TikTokPixel { page: () => void instance: (pixel_code: string) => TikTokPixel - identify: ({ - email, - phone_number, - external_id, - first_name, - last_name, - city, - state, - country, - zip_code - }: TTUser) => void + identify: (user: TTUser) => void track: ( event: string, - { - contents, - content_type, - currency, - value, - query, - description, - order_id, - shop_id, - content_ids, - num_items, - search_string, - city, - region, - country, - checkin_date, - checkout_date, - num_adults, - num_children, - num_infants, - suggested_hotels, - departing_departure_date, - returning_departure_date, - origin_airport, - destination_airiport, - destination_ids, - departing_arrival_date, - returning_arrival_date, - travel_class, - user_score, - preferred_num_stops, - travel_start, - travel_end, - suggested_destinations, - postal_code, - make, - model, - year, - state_of_vehicle, - mileage, - exterior_color, - transmission, - body_style, - fuel_type, - drivetrain, - preferred_price_range, - trim, - vin, - interior_color, - condition_of_vehicle, - viewcontent_type, - search_type, - registration_type - }: TTBaseProps & TTTravelProps & TTAutoProps, - { - event_id - }: { - event_id: string | undefined - } + { ...props }: TTBaseProps & TTTravelProps & TTAutoProps, + { event_id }: { event_id?: string } ) => void } diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts index 7ce7a876f8b..d2511946fce 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/utils.ts @@ -2,11 +2,7 @@ import { Payload } from './reportWebEvent/generated-types' import { formatPhone, handleArrayInput, formatString, formatAddress } from './formatter' import { TTUser } from './types' -export function getUserObject(payload: Payload): TTUser { - return buildUserObject(payload) -} - -function buildUserObject(payload: Payload): TTUser { +export function getUser(payload: Payload): TTUser { return { email: handleArrayInput(payload.email), phone_number: formatPhone(handleArrayInput(payload.phone_number)), @@ -18,4 +14,4 @@ function buildUserObject(payload: Payload): TTUser { country: formatAddress(payload.address?.country), zip_code: formatString(payload.address?.zip_code) } -} +} \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/tiktok-conversions/reportWebEvent/fields/travel_fields.ts b/packages/destination-actions/src/destinations/tiktok-conversions/reportWebEvent/fields/travel_fields.ts index a225c5428c9..d095571a64c 100644 --- a/packages/destination-actions/src/destinations/tiktok-conversions/reportWebEvent/fields/travel_fields.ts +++ b/packages/destination-actions/src/destinations/tiktok-conversions/reportWebEvent/fields/travel_fields.ts @@ -173,8 +173,8 @@ export const travel_fields: InputField = { origin_airport: { '@path': '$.properties.origin_airport' }, - destination_airiport: { - '@path': '$.properties.destination_airiport' + destination_airport: { + '@path': '$.properties.destination_airport' }, destination_ids: { '@path': '$.properties.destination_ids' // Confirmed this can be a single string or an array of strings From 301ba976d5caab70efc4192210029584f27970b6 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Fri, 24 Oct 2025 12:52:14 +0100 Subject: [PATCH 4/8] updating breaking test --- .../tiktok-pixel/src/identify/index.ts | 14 +++++++------- .../src/reportWebEvent/fields/common_fields.ts | 2 +- .../tiktok-pixel/src/reportWebEvent/index.ts | 4 ++-- .../tiktok-conversions/__tests__/index.test.ts | 1 + 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts index 2767a001a36..4e211a388d2 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/identify/index.ts @@ -3,7 +3,7 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import { getUser } from '../utils' import { TikTokPixel } from '../types' -import { commonFields } from '../reportWebEvent/fields/common_fields' +import { common_fields } from '../reportWebEvent/fields/common_fields' // Change from unknown to the partner SDK types const action: BrowserActionDefinition = { @@ -13,25 +13,25 @@ const action: BrowserActionDefinition = { defaultSubscription: 'type = "identify"', platform: 'web', fields: { - ...commonFields, + ...common_fields, phone_number: { - ...commonFields.phone_number, + ...common_fields.phone_number, default: { '@path': '$.traits.phone' } }, email: { - ...commonFields.email, + ...common_fields.email, default: { '@path': '$.traits.email' } }, first_name: { - ...commonFields.first_name, + ...common_fields.first_name, default: { '@path': '$.traits.first_name' } }, last_name: { - ...commonFields.last_name, + ...common_fields.last_name, default: { '@path': '$.traits.last_name' } }, address: { - ...commonFields.address, + ...common_fields.address, default: { city: { '@path': '$.traits.address.city' }, country: { '@path': '$.traits.address.country' }, diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts index 772e9c37743..fff28fc1582 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/fields/common_fields.ts @@ -1,7 +1,7 @@ import { InputField } from '@segment/actions-core' import { VEHICLE_FIELDS, TRAVEL_FIELDS } from '../constants' -export const commonFields: Record = { +export const common_fields: Record = { event_spec_type: { label: 'Additional Fields', type: 'string', diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts index f464877ccc4..1436103f4b5 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/index.ts @@ -4,7 +4,7 @@ import type { Payload } from './generated-types' import { getUser } from '../utils' import { getAllProperties } from './utils' import { TikTokPixel } from '../types' -import { commonFields } from './fields/common_fields' +import { common_fields } from './fields/common_fields' import { travel_fields } from './fields/travel_fields' import { vehicle_fields } from './fields/vehicle_fields' @@ -15,7 +15,7 @@ const action: BrowserActionDefinition = { platform: 'web', defaultSubscription: 'type = "track"', fields: { - ...commonFields, + ...common_fields, vehicle_fields, travel_fields }, diff --git a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts index 22d194ae749..63709cdb347 100644 --- a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts @@ -954,6 +954,7 @@ describe('Tiktok Conversions', () => { departing_departure_date: '20250901', returning_departure_date: '20250901', origin_airport: 'test_origin_airport', + destination_airport: 'test_destination_airport', destination_ids: ['destination_ids_1', 'destination_ids_2'], departing_arrival_date: '20250901', returning_arrival_date: '20250901', From 43e53552f62e3529e9c7cd179b920dc7f1340b9c Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Fri, 24 Oct 2025 12:53:51 +0100 Subject: [PATCH 5/8] undoing test fix --- .../src/destinations/tiktok-conversions/__tests__/index.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts index 63709cdb347..22d194ae749 100644 --- a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts @@ -954,7 +954,6 @@ describe('Tiktok Conversions', () => { departing_departure_date: '20250901', returning_departure_date: '20250901', origin_airport: 'test_origin_airport', - destination_airport: 'test_destination_airport', destination_ids: ['destination_ids_1', 'destination_ids_2'], departing_arrival_date: '20250901', returning_arrival_date: '20250901', From 2eb8d30d16c05d48319a4c2b1a6b1adbffd65ecb Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Fri, 24 Oct 2025 12:55:36 +0100 Subject: [PATCH 6/8] updating breaking test --- .../src/destinations/tiktok-conversions/__tests__/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts index 22d194ae749..63709cdb347 100644 --- a/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/tiktok-conversions/__tests__/index.test.ts @@ -954,6 +954,7 @@ describe('Tiktok Conversions', () => { departing_departure_date: '20250901', returning_departure_date: '20250901', origin_airport: 'test_origin_airport', + destination_airport: 'test_destination_airport', destination_ids: ['destination_ids_1', 'destination_ids_2'], departing_arrival_date: '20250901', returning_arrival_date: '20250901', From 77077871b6e7fef1b5cc7b0bf3d70ac0868445a3 Mon Sep 17 00:00:00 2001 From: Jaehyuk Rhee Date: Mon, 10 Nov 2025 12:49:11 -0500 Subject: [PATCH 7/8] add unit test --- .../reportWebEvent/__tests__/index.test.ts | 484 ++++++++++++++++++ 1 file changed, 484 insertions(+) diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts index 3fbf37a2f0f..bbab0791221 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts @@ -679,4 +679,488 @@ describe('TikTokPixel.reportWebEvent', () => { { event_id: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6' } ) }) + + test('maps properties correctly for "ViewContent" event for travel parameters', async () => { + const subscriptions: Subscription[] = [ + { + partnerAction: 'reportWebEvent', + name: 'View Content', + enabled: true, + subscribe: 'type="page"', + mapping: { + event_id: { + '@path': '$.messageId' + }, + anonymousId: { + '@path': '$.anonymousId' + }, + external_id: { + '@path': '$.userId' + }, + phone_number: { + '@path': '$.properties.phone' + }, + email: { + '@path': '$.properties.email' + }, + last_name: { + '@path': '$.context.traits.last_name' + }, + first_name: { + '@path': '$.context.traits.first_name' + }, + address: { + city: { + '@path': '$.context.traits.address.city' + }, + state: { + '@path': '$.context.traits.address.state' + }, + country: { + '@path': '$.context.traits.address.country' + } + }, + groupId: { + '@path': '$.groupId' + }, + event: 'ViewContent', + contents: { + '@arrayPath': [ + '$.properties', + { + price: { + '@path': '$.price' + }, + quantity: { + '@path': '$.quantity' + }, + content_type: { + '@path': '$.category' + }, + content_id: { + '@path': '$.product_id' + } + } + ] + }, + currency: { + '@path': '$.properties.currency' + }, + value: { + '@path': '$.properties.value' + }, + query: { + '@path': '$.properties.query' + }, + description: { + '@path': '$.properties.description' + }, + city: { + '@path': '$.properties.city' + }, + region: { + '@path': '$.properties.region' + }, + country: { + '@path': '$.properties.country' + }, + checkin_date: { + '@path': '$.properties.checkin_date' + }, + checkout_date: { + '@path': '$.properties.checkout_date' + }, + num_adults: { + '@path': '$.properties.num_adults' + }, + num_children: { + '@path': '$.properties.num_children' + }, + num_infants: { + '@path': '$.properties.num_infants' + }, + suggested_hotels: { + '@path': '$.properties.suggested_hotels' + }, + departing_departure_date: { + '@path': '$.properties.departing_departure_date' + }, + returning_departure_date: { + '@path': '$.properties.returning_departure_date' + }, + origin_airport: { + '@path': '$.properties.origin_airport' + }, + destination_airport: { + '@path': '$.properties.destination_airport' + }, + destination_ids: { + '@path': '$.properties.destination_ids' + }, + departing_arrival_date: { + '@path': '$.properties.departing_arrival_date' + }, + returning_arrival_date: { + '@path': '$.properties.returning_arrival_date' + }, + travel_class: { + '@path': '$.properties.travel_class' + }, + user_score: { + '@path': '$.properties.user_score' + }, + preferred_num_stops: { + '@path': '$.properties.preferred_num_stops' + }, + travel_start: { + '@path': '$.properties.travel_start' + }, + travel_end: { + '@path': '$.properties.travel_end' + }, + suggested_destinations: { + '@path': '$.properties.suggested_destinations' + } + } + } + ] + + const context = new Context({ + messageId: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6', + type: 'page', + anonymousId: 'anonymousId', + userId: 'userId', + context: { + traits: { + last_name: 'lastName', + first_name: 'firstName', + address: { + city: 'city', + state: 'state', + country: 'country' + } + } + }, + properties: { + product_id: '123', + category: 'product', + quantity: 1, + price: 1, + query: 'test-query', + value: 10, + currency: 'USD', + phone: ['+12345678900'], + email: ['aaa@aaa.com'], + description: 'test-description', + city: 'test_city', + region: 'test_region', + country: 'test_country', + checkin_date: 'test_checkin_date', + checkout_date: 'test_checkout_date', + num_adults: 1, + num_children: 1, + num_infants: 1, + suggested_hotels: ['test_suggested_hotels_1', 'test_suggested_hotels_2'], + departing_departure_date: '20250901', + returning_departure_date: '20250901', + origin_airport: 'test_origin_airport', + destination_airport: 'test_destination_airport', + destination_ids: ['destination_ids_1', 'destination_ids_2'], + departing_arrival_date: '20250901', + returning_arrival_date: '20250901', + travel_class: 'eco', + user_score: 1, + preferred_num_stops: 0, + travel_start: '20250901', + travel_end: '20250901', + suggested_destinations: ['suggested_destinations_1', 'suggested_destinations_2'] + } + }) + + const [webEvent] = await TikTokDestination({ + ...settings, + subscriptions + }) + reportWebEvent = webEvent + + await reportWebEvent.load(Context.system(), {} as Analytics) + await reportWebEvent.track?.(context) + + expect(mockTtp.identify).toHaveBeenCalledWith({ + city: 'city', + country: 'country', + email: 'aaa@aaa.com', + phone_number: '+12345678900', + external_id: 'userId', + first_name: 'firstname', + last_name: 'lastname', + state: 'state', + zip_code: '' + }) + expect(mockTtp.track).toHaveBeenCalledWith( + 'ViewContent', + { + contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + currency: 'USD', + description: 'test-description', + query: 'test-query', + value: 10, + city: 'test_city', + region: 'test_region', + country: 'test_country', + checkin_date: 'test_checkin_date', + checkout_date: 'test_checkout_date', + num_adults: 1, + num_children: 1, + num_infants: 1, + suggested_hotels: ['test_suggested_hotels_1', 'test_suggested_hotels_2'], + departing_departure_date: '20250901', + returning_departure_date: '20250901', + origin_airport: 'test_origin_airport', + destination_airport: 'test_destination_airport', + destination_ids: ['destination_ids_1', 'destination_ids_2'], + departing_arrival_date: '20250901', + returning_arrival_date: '20250901', + travel_class: 'eco', + user_score: 1, + preferred_num_stops: 0, + travel_start: '20250901', + travel_end: '20250901', + suggested_destinations: ['suggested_destinations_1', 'suggested_destinations_2'] + }, + { event_id: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6' } + ) + }) + + test('maps properties correctly for "ViewContent" event with auto parameters', async () => { + const subscriptions: Subscription[] = [ + { + partnerAction: 'reportWebEvent', + name: 'View Content', + enabled: true, + subscribe: 'type="page"', + mapping: { + event_id: { + '@path': '$.messageId' + }, + anonymousId: { + '@path': '$.anonymousId' + }, + external_id: { + '@path': '$.userId' + }, + phone_number: { + '@path': '$.properties.phone' + }, + email: { + '@path': '$.properties.email' + }, + last_name: { + '@path': '$.context.traits.last_name' + }, + first_name: { + '@path': '$.context.traits.first_name' + }, + address: { + city: { + '@path': '$.context.traits.address.city' + }, + state: { + '@path': '$.context.traits.address.state' + }, + country: { + '@path': '$.context.traits.address.country' + } + }, + groupId: { + '@path': '$.groupId' + }, + event: 'ViewContent', + contents: { + '@arrayPath': [ + '$.properties', + { + price: { + '@path': '$.price' + }, + quantity: { + '@path': '$.quantity' + }, + content_type: { + '@path': '$.category' + }, + content_id: { + '@path': '$.product_id' + } + } + ] + }, + currency: { + '@path': '$.properties.currency' + }, + value: { + '@path': '$.properties.value' + }, + query: { + '@path': '$.properties.query' + }, + description: { + '@path': '$.properties.description' + }, + postal_code: { + '@path': '$.properties.postal_code' + }, + make: { + '@path': '$.properties.make' + }, + model: { + '@path': '$.properties.model' + }, + year: { + '@path': '$.properties.year' + }, + state_of_vehicle: { + '@path': '$.properties.state_of_vehicle' + }, + mileage_value: { + '@path': '$.properties.mileage_value' + }, + mileage_unit: { + '@path': '$.properties.mileage_unit' + }, + exterior_color: { + '@path': '$.properties.exterior_color' + }, + transmission: { + '@path': '$.properties.transmission' + }, + body_style: { + '@path': '$.properties.body_style' + }, + fuel_type: { + '@path': '$.properties.fuel_type' + }, + drivetrain: { + '@path': '$.properties.drivetrain' + }, + preferred_price_range_min: { + '@path': '$.properties.preferred_price_range_min' + }, + preferred_price_range_max: { + '@path': '$.properties.preferred_price_range_max' + }, + trim: { + '@path': '$.properties.trim' + }, + vin: { + '@path': '$.properties.vin' + }, + interior_color: { + '@path': '$.properties.interior_color' + }, + condition_of_vehicle: { + '@path': '$.properties.condition_of_vehicle' + } + } + } + ] + + const context = new Context({ + messageId: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6', + type: 'page', + anonymousId: 'anonymousId', + userId: 'userId', + context: { + traits: { + last_name: 'lastName', + first_name: 'firstName', + address: { + city: 'city', + state: 'state', + country: 'country' + } + } + }, + properties: { + product_id: '123', + category: 'product', + quantity: 1, + price: 1, + query: 'test-query', + value: 10, + currency: 'USD', + phone: ['+12345678900'], + email: ['aaa@aaa.com'], + description: 'test-description', + postal_code: 'test_postal_code', + make: 'test_make', + model: 'test_model', + year: 2020, + state_of_vehicle: 'New', + mileage_value: 12345, + mileage_unit: 'MI', + exterior_color: 'test_exterior_color', + transmission: 'Automatic', + body_style: 'Coupe', + fuel_type: 'Diesel', + drivetrain: 'AWD', + preferred_price_range_min: 1000, + preferred_price_range_max: 2000, + trim: 'test_trim', + vin: 'test_vin', + interior_color: 'test_interior_color', + condition_of_vehicle: 'Good' + } + }) + + const [webEvent] = await TikTokDestination({ + ...settings, + subscriptions + }) + reportWebEvent = webEvent + + await reportWebEvent.load(Context.system(), {} as Analytics) + await reportWebEvent.track?.(context) + + expect(mockTtp.identify).toHaveBeenCalledWith({ + city: 'city', + country: 'country', + email: 'aaa@aaa.com', + phone_number: '+12345678900', + external_id: 'userId', + first_name: 'firstname', + last_name: 'lastname', + state: 'state', + zip_code: '' + }) + expect(mockTtp.track).toHaveBeenCalledWith( + 'ViewContent', + { + contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + currency: 'USD', + description: 'test-description', + query: 'test-query', + value: 10, + postal_code: 'test_postal_code', + make: 'test_make', + model: 'test_model', + year: 2020, + state_of_vehicle: 'New', + mileage_value: 12345, + mileage_unit: 'MI', + exterior_color: 'test_exterior_color', + transmission: 'Automatic', + body_style: 'Coupe', + fuel_type: 'Diesel', + drivetrain: 'AWD', + preferred_price_range_min: 1000, + preferred_price_range_max: 2000, + trim: 'test_trim', + vin: 'test_vin', + interior_color: 'test_interior_color', + condition_of_vehicle: 'Good' + }, + { event_id: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6' } + ) + }) }) From 843979f614012e54049c73aa3c6b2f2a570e3817 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Tue, 11 Nov 2025 13:01:00 +0000 Subject: [PATCH 8/8] unit tests passing --- .../reportWebEvent/__tests__/index.test.ts | 348 +++++++++--------- .../tiktok-pixel/src/reportWebEvent/utils.ts | 14 +- 2 files changed, 188 insertions(+), 174 deletions(-) diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts index bbab0791221..d50a6c3ba01 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/__tests__/index.test.ts @@ -2,6 +2,7 @@ import { Analytics, Context } from '@segment/analytics-next' import { Subscription } from '@segment/browser-destination-runtime' import TikTokDestination, { destination } from '../../index' import { TikTokPixel } from '../../types' +import { TRAVEL_FIELDS, VEHICLE_FIELDS } from '../constants' describe('TikTokPixel.reportWebEvent', () => { const settings = { @@ -68,14 +69,8 @@ describe('TikTokPixel.reportWebEvent', () => { expect(mockTtp.track).toHaveBeenCalledWith( 'Pageview', { - content_type: undefined, contents: [], - currency: 'USD', - description: undefined, - order_id: undefined, - query: undefined, - shop_id: undefined, - value: undefined + currency: 'USD' }, { event_id: '' @@ -137,7 +132,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -183,13 +178,13 @@ describe('TikTokPixel.reportWebEvent', () => { products: [ { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1 }, { product_id: '456', - category: 'product', + category: 'category1', quantity: 2, price: 2 } @@ -227,8 +222,8 @@ describe('TikTokPixel.reportWebEvent', () => { 'PlaceAnOrder', { contents: [ - { content_id: '123', content_type: 'product', price: 1, quantity: 1 }, - { content_id: '456', content_type: 'product', price: 2, quantity: 2 } + { content_id: '123', content_category: 'category1', price: 1, quantity: 1 }, + { content_id: '456', content_category: 'category1', price: 2, quantity: 2 } ], currency: 'USD', description: 'test-description', @@ -293,7 +288,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -337,7 +332,7 @@ describe('TikTokPixel.reportWebEvent', () => { }, properties: { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1, query: 'test-query', @@ -372,7 +367,7 @@ describe('TikTokPixel.reportWebEvent', () => { expect(mockTtp.track).toHaveBeenCalledWith( 'AddToCart', { - contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + contents: [{ content_id: '123', content_category: 'category1', price: 1, quantity: 1 }], currency: 'USD', description: 'test-description', query: 'test-query', @@ -436,7 +431,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -479,7 +474,7 @@ describe('TikTokPixel.reportWebEvent', () => { }, properties: { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1, query: 'test-query', @@ -514,7 +509,7 @@ describe('TikTokPixel.reportWebEvent', () => { expect(mockTtp.track).toHaveBeenCalledWith( 'ViewContent', { - contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + contents: [{ content_id: '123', content_category: 'category1', price: 1, quantity: 1 }], currency: 'USD', description: 'test-description', query: 'test-query', @@ -578,7 +573,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -624,13 +619,13 @@ describe('TikTokPixel.reportWebEvent', () => { products: [ { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1 }, { product_id: '456', - category: 'product', + category: 'category1', quantity: 2, price: 2 } @@ -668,8 +663,8 @@ describe('TikTokPixel.reportWebEvent', () => { 'PlaceAnOrder', { contents: [ - { content_id: '123', content_type: 'product', price: 1, quantity: 1 }, - { content_id: '456', content_type: 'product', price: 2, quantity: 2 } + { content_id: '123', content_category: 'category1', price: 1, quantity: 1 }, + { content_id: '456', content_category: 'category1', price: 2, quantity: 2 } ], currency: 'USD', description: 'test-description', @@ -688,6 +683,7 @@ describe('TikTokPixel.reportWebEvent', () => { enabled: true, subscribe: 'type="page"', mapping: { + event_spec_type: TRAVEL_FIELDS, event_id: { '@path': '$.messageId' }, @@ -734,7 +730,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -755,71 +751,73 @@ describe('TikTokPixel.reportWebEvent', () => { description: { '@path': '$.properties.description' }, - city: { - '@path': '$.properties.city' - }, - region: { - '@path': '$.properties.region' - }, - country: { - '@path': '$.properties.country' - }, - checkin_date: { - '@path': '$.properties.checkin_date' - }, - checkout_date: { - '@path': '$.properties.checkout_date' - }, - num_adults: { - '@path': '$.properties.num_adults' - }, - num_children: { - '@path': '$.properties.num_children' - }, - num_infants: { - '@path': '$.properties.num_infants' - }, - suggested_hotels: { - '@path': '$.properties.suggested_hotels' - }, - departing_departure_date: { - '@path': '$.properties.departing_departure_date' - }, - returning_departure_date: { - '@path': '$.properties.returning_departure_date' - }, - origin_airport: { - '@path': '$.properties.origin_airport' - }, - destination_airport: { - '@path': '$.properties.destination_airport' - }, - destination_ids: { - '@path': '$.properties.destination_ids' - }, - departing_arrival_date: { - '@path': '$.properties.departing_arrival_date' - }, - returning_arrival_date: { - '@path': '$.properties.returning_arrival_date' - }, - travel_class: { - '@path': '$.properties.travel_class' - }, - user_score: { - '@path': '$.properties.user_score' - }, - preferred_num_stops: { - '@path': '$.properties.preferred_num_stops' - }, - travel_start: { - '@path': '$.properties.travel_start' - }, - travel_end: { - '@path': '$.properties.travel_end' - }, - suggested_destinations: { - '@path': '$.properties.suggested_destinations' + travel_fields: { + city: { + '@path': '$.properties.city' + }, + region: { + '@path': '$.properties.region' + }, + country: { + '@path': '$.properties.country' + }, + checkin_date: { + '@path': '$.properties.checkin_date' + }, + checkout_date: { + '@path': '$.properties.checkout_date' + }, + num_adults: { + '@path': '$.properties.num_adults' + }, + num_children: { + '@path': '$.properties.num_children' + }, + num_infants: { + '@path': '$.properties.num_infants' + }, + suggested_hotels: { + '@path': '$.properties.suggested_hotels' // Confirmed this can be a single string or an array of strings + }, + departing_departure_date: { + '@path': '$.properties.departing_departure_date' + }, + returning_departure_date: { + '@path': '$.properties.returning_departure_date' + }, + origin_airport: { + '@path': '$.properties.origin_airport' + }, + destination_airport: { + '@path': '$.properties.destination_airport' + }, + destination_ids: { + '@path': '$.properties.destination_ids' // Confirmed this can be a single string or an array of strings + }, + departing_arrival_date: { + '@path': '$.properties.departing_arrival_date' + }, + returning_arrival_date: { + '@path': '$.properties.returning_arrival_date' + }, + travel_class: { + '@path': '$.properties.travel_class' + }, + user_score: { + '@path': '$.properties.user_score' + }, + preferred_num_stops: { + '@path': '$.properties.preferred_num_stops' + }, + travel_start: { + '@path': '$.properties.travel_start' + }, + travel_end: { + '@path': '$.properties.travel_end' + }, + suggested_destinations: { + '@path': '$.properties.suggested_destinations' // Confirmed this can be a single string or an array of strings + } } } } @@ -843,7 +841,7 @@ describe('TikTokPixel.reportWebEvent', () => { }, properties: { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1, query: 'test-query', @@ -900,33 +898,36 @@ describe('TikTokPixel.reportWebEvent', () => { expect(mockTtp.track).toHaveBeenCalledWith( 'ViewContent', { - contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + contents: [{ content_id: '123', content_category: 'category1', price: 1, quantity: 1 }], + country: "test_country", currency: 'USD', + departing_arrival_date: "20250901", + departing_departure_date: "20250901", description: 'test-description', - query: 'test-query', - value: 10, - city: 'test_city', - region: 'test_region', - country: 'test_country', - checkin_date: 'test_checkin_date', - checkout_date: 'test_checkout_date', + destination_airport: "test_destination_airport", + destination_ids: [ + "destination_ids_1", + "destination_ids_2" + ], num_adults: 1, num_children: 1, - num_infants: 1, - suggested_hotels: ['test_suggested_hotels_1', 'test_suggested_hotels_2'], - departing_departure_date: '20250901', - returning_departure_date: '20250901', - origin_airport: 'test_origin_airport', - destination_airport: 'test_destination_airport', - destination_ids: ['destination_ids_1', 'destination_ids_2'], - departing_arrival_date: '20250901', + num_infants:1, + origin_airport: "test_origin_airport", + preferred_num_stops: 0, + region: 'test_region', returning_arrival_date: '20250901', + returning_departure_date: '20250901', + suggested_destinations: ['suggested_destinations_1', 'suggested_destinations_2'], + suggested_hotels: ['test_suggested_hotels_1', 'test_suggested_hotels_2'], travel_class: 'eco', - user_score: 1, - preferred_num_stops: 0, travel_start: '20250901', travel_end: '20250901', - suggested_destinations: ['suggested_destinations_1', 'suggested_destinations_2'] + user_score: 1, + query: 'test-query', + value: 10, + city: 'test_city', + checkin_date: 'test_checkin_date', + checkout_date: 'test_checkout_date' }, { event_id: 'ajs-71f386523ee5dfa90c7d0fda28b6b5c6' } ) @@ -940,6 +941,7 @@ describe('TikTokPixel.reportWebEvent', () => { enabled: true, subscribe: 'type="page"', mapping: { + event_spec_type: VEHICLE_FIELDS, event_id: { '@path': '$.messageId' }, @@ -986,7 +988,7 @@ describe('TikTokPixel.reportWebEvent', () => { quantity: { '@path': '$.quantity' }, - content_type: { + content_category: { '@path': '$.category' }, content_id: { @@ -1010,56 +1012,70 @@ describe('TikTokPixel.reportWebEvent', () => { postal_code: { '@path': '$.properties.postal_code' }, - make: { - '@path': '$.properties.make' - }, - model: { - '@path': '$.properties.model' - }, - year: { - '@path': '$.properties.year' - }, - state_of_vehicle: { - '@path': '$.properties.state_of_vehicle' - }, - mileage_value: { - '@path': '$.properties.mileage_value' - }, - mileage_unit: { - '@path': '$.properties.mileage_unit' - }, - exterior_color: { - '@path': '$.properties.exterior_color' - }, - transmission: { - '@path': '$.properties.transmission' - }, - body_style: { - '@path': '$.properties.body_style' - }, - fuel_type: { - '@path': '$.properties.fuel_type' - }, - drivetrain: { - '@path': '$.properties.drivetrain' - }, - preferred_price_range_min: { - '@path': '$.properties.preferred_price_range_min' - }, - preferred_price_range_max: { - '@path': '$.properties.preferred_price_range_max' - }, - trim: { - '@path': '$.properties.trim' - }, - vin: { - '@path': '$.properties.vin' - }, - interior_color: { - '@path': '$.properties.interior_color' - }, - condition_of_vehicle: { - '@path': '$.properties.condition_of_vehicle' + vehicle_fields: { + postal_code: { + '@path': '$.properties.postal_code' + }, + make: { + '@path': '$.properties.make' + }, + model: { + '@path': '$.properties.model' + }, + year: { + '@path': '$.properties.year' + }, + state_of_vehicle: { + '@path': '$.properties.state_of_vehicle' + }, + mileage_value: { + '@path': '$.properties.mileage_value' + }, + mileage_unit: { + '@path': '$.properties.mileage_unit' + }, + exterior_color: { + '@path': '$.properties.exterior_color' + }, + transmission: { + '@path': '$.properties.transmission' + }, + body_style: { + '@path': '$.properties.body_style' + }, + fuel_type: { + '@path': '$.properties.fuel_type' + }, + drivetrain: { + '@path': '$.properties.drive_train' + }, + preferred_price_range_min: { + '@path': '$.properties.preferred_price_range_min' + }, + preferred_price_range_max: { + '@path': '$.properties.preferred_price_range_max' + }, + trim: { + '@path': '$.properties.trim' + }, + vin: { + '@path': '$.properties.vin' + }, + interior_color: { + '@path': '$.properties.interior_color' + }, + condition_of_vehicle: { + '@path': '$.properties.condition_of_vehicle' + }, + viewcontent_type: { + '@path': '$.properties.viewcontent_type' + }, + search_type: { + '@path': '$.properties.search_type' + }, + registration_type: { + '@path': '$.properties.registration_type' + } } } } @@ -1083,7 +1099,7 @@ describe('TikTokPixel.reportWebEvent', () => { }, properties: { product_id: '123', - category: 'product', + category: 'category1', quantity: 1, price: 1, query: 'test-query', @@ -1103,7 +1119,7 @@ describe('TikTokPixel.reportWebEvent', () => { transmission: 'Automatic', body_style: 'Coupe', fuel_type: 'Diesel', - drivetrain: 'AWD', + drive_train: 'AWD', preferred_price_range_min: 1000, preferred_price_range_max: 2000, trim: 'test_trim', @@ -1136,7 +1152,7 @@ describe('TikTokPixel.reportWebEvent', () => { expect(mockTtp.track).toHaveBeenCalledWith( 'ViewContent', { - contents: [{ content_id: '123', content_type: 'product', price: 1, quantity: 1 }], + contents: [{ content_id: '123', content_category: 'category1', price: 1, quantity: 1 }], currency: 'USD', description: 'test-description', query: 'test-query', @@ -1146,15 +1162,13 @@ describe('TikTokPixel.reportWebEvent', () => { model: 'test_model', year: 2020, state_of_vehicle: 'New', - mileage_value: 12345, - mileage_unit: 'MI', + mileage: { unit: 'MI', value: 12345 }, exterior_color: 'test_exterior_color', transmission: 'Automatic', body_style: 'Coupe', fuel_type: 'Diesel', drivetrain: 'AWD', - preferred_price_range_min: 1000, - preferred_price_range_max: 2000, + preferred_price_range: [1000, 2000], trim: 'test_trim', vin: 'test_vin', interior_color: 'test_interior_color', diff --git a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts index df4a41530c1..66a968de882 100644 --- a/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts +++ b/packages/browser-destinations/destinations/tiktok-pixel/src/reportWebEvent/utils.ts @@ -30,16 +30,16 @@ function getProps(payload: Payload): TTBaseProps { const requestProperties: TTBaseProps = { contents: contents ? contents.map(({ price, quantity, content_category, content_id, content_name, brand }) => ({ - price: price ?? undefined, - quantity: quantity ?? undefined, - content_category: content_category ?? undefined, - content_id: content_id ?? undefined, - content_name: content_name ?? undefined, - brand: brand ?? undefined + ...(price !== undefined && { price }), + ...(quantity !== undefined && { quantity }), + ...(content_category !== undefined && { content_category }), + ...(content_id !== undefined && { content_id }), + ...(content_name !== undefined && { content_name }), + ...(brand !== undefined && { brand }) })) : [], ...(content_type !== undefined && { content_type }), - ...(currency !== undefined && { currency }), + ...( { currency: currency ?? 'USD' } ), ...(value !== undefined && { value }), ...(query !== undefined && { query }), ...(description !== undefined && { description }),