From d0c6c9dda89bed3882e660b56fb063af2ef67b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADn=20Alcaraz?= Date: Wed, 3 Jul 2024 14:31:21 -0700 Subject: [PATCH] Revert "Add features to bring parity with klaviyo classic" (#2134) This reverts commit 0a28756494699bbe24e93eca7c1ac137b448f7c1. --- .../__snapshots__/snapshot.test.ts.snap | 23 +- .../__snapshots__/snapshot.test.ts.snap | 23 +- .../__tests__/formatter.test.ts | 198 -------------- .../orderCompleted/__tests__/index.test.ts | 217 +++------------ .../klaviyo/orderCompleted/formatters.ts | 77 ------ .../klaviyo/orderCompleted/generated-types.ts | 38 +-- .../klaviyo/orderCompleted/index.ts | 253 +++++------------- .../klaviyo/orderCompleted/types.ts | 3 - .../destinations/klaviyo/trackEvent/index.ts | 2 +- 9 files changed, 107 insertions(+), 727 deletions(-) delete mode 100644 packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/formatter.test.ts delete mode 100644 packages/destination-actions/src/destinations/klaviyo/orderCompleted/formatters.ts delete mode 100644 packages/destination-actions/src/destinations/klaviyo/orderCompleted/types.ts diff --git a/packages/destination-actions/src/destinations/klaviyo/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/klaviyo/__tests__/__snapshots__/snapshot.test.ts.snap index 557cd14a73..38747523a1 100644 --- a/packages/destination-actions/src/destinations/klaviyo/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/klaviyo/__tests__/__snapshots__/snapshot.test.ts.snap @@ -54,28 +54,7 @@ Object { }, }, "properties": Object { - "Categories": Array [ - "PE*zlOgIPA]mVozMLBaL", - ], - "ItemNames": Array [ - "PE*zlOgIPA]mVozMLBaL", - ], - "Items": Array [ - Object { - "Categories": Array [ - "PE*zlOgIPA]mVozMLBaL", - ], - "ImageURL": "PE*zlOgIPA]mVozMLBaL", - "ItemPrice": 83414764297912.31, - "ProductId": "PE*zlOgIPA]mVozMLBaL", - "ProductName": "PE*zlOgIPA]mVozMLBaL", - "ProductURL": "PE*zlOgIPA]mVozMLBaL", - "Quantity": 83414764297912.31, - "RowTotal": 83414764297912.31, - "SKU": "PE*zlOgIPA]mVozMLBaL", - }, - ], - "OrderId": "PE*zlOgIPA]mVozMLBaL", + "testType": "PE*zlOgIPA]mVozMLBaL", }, "time": "2021-02-01T00:00:00.000Z", "unique_id": "PE*zlOgIPA]mVozMLBaL", diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/__snapshots__/snapshot.test.ts.snap index 2fedb3782f..b92d52102d 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/__snapshots__/snapshot.test.ts.snap @@ -24,28 +24,7 @@ Object { }, }, "properties": Object { - "Categories": Array [ - "923^%f]tQn]lN2o", - ], - "ItemNames": Array [ - "923^%f]tQn]lN2o", - ], - "Items": Array [ - Object { - "Categories": Array [ - "923^%f]tQn]lN2o", - ], - "ImageURL": "923^%f]tQn]lN2o", - "ItemPrice": 28946631197982.72, - "ProductId": "923^%f]tQn]lN2o", - "ProductName": "923^%f]tQn]lN2o", - "ProductURL": "923^%f]tQn]lN2o", - "Quantity": 28946631197982.72, - "RowTotal": 28946631197982.72, - "SKU": "923^%f]tQn]lN2o", - }, - ], - "OrderId": "923^%f]tQn]lN2o", + "testType": "923^%f]tQn]lN2o", }, "time": "2021-02-01T00:00:00.000Z", "unique_id": "923^%f]tQn]lN2o", diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/formatter.test.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/formatter.test.ts deleted file mode 100644 index 2a9b735bbe..0000000000 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/formatter.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { formatOrderedProduct, formatProductItems, convertKeysToTitleCase } from '../formatters' -import { Product } from '../types' - -describe('formatProductItems', () => { - it('should format product items', () => { - const product: Product = { - product_id: 'product_id', - sku: 'sku', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore', - array: ['value1', 'value2'], - shippingAddress: { - street: 'street', - city: 'city', - state: 'state', - postalCode: 'postal_code', - country: 'country', - nested: { - key: 'value' - } - } - } - - expect(formatProductItems(product)).toEqual({ - ProductId: 'product_id', - SKU: 'sku', - ProductName: 'name', - Quantity: 1, - ItemPrice: 10, - RowTotal: 10, - Categories: ['category'], - ProductURL: 'url', - ImageURL: 'image_url', - PropertyWithSpace: 'space', - PropertyWithUnderscore: 'underscore', - Array: ['value1', 'value2'], - ShippingAddress: { - Street: 'street', - City: 'city', - State: 'state', - PostalCode: 'postal_code', - Country: 'country', - Nested: { - key: 'value' - } - } - }) - }) - it('should use id attribute for ProductId if product_id is not present', () => { - const product: Product = { - id: 'random_id', - sku: 'sku', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - const result = formatOrderedProduct(product) - expect(result.productProperties.ProductId).toEqual('random_id') - }) -}) - -describe('formatOrderedProduct', () => { - it('should format ordered product', () => { - const product: Product = { - product_id: 'product_id', - sku: 'sku', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - expect(formatOrderedProduct(product, 'order_id')).toEqual({ - unique_id: 'order_id_product_id', - productProperties: { - OrderId: 'order_id', - ProductId: 'product_id', - SKU: 'sku', - ProductName: 'name', - Quantity: 1, - Categories: ['category'], - ProductURL: 'url', - ImageURL: 'image_url', - PropertyWithSpace: 'space', - PropertyWithUnderscore: 'underscore' - } - }) - }) - - it('should use sku instead for unique_id if product_id is not present', () => { - const product: Product = { - sku: 'sku', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - const result = formatOrderedProduct(product, 'order_id') - expect(result.unique_id).toEqual('order_id_sku') - }) - - it('should use sku for unique_id if product_id is not present', () => { - const product: Product = { - sku: 'sku', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - const result = formatOrderedProduct(product, 'order_id') - expect(result.unique_id).toEqual('order_id_sku') - }) - - it('should use id attribute for unique_id if product_id and sku are not present', () => { - const product: Product = { - id: 'random_id', - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - const result = formatOrderedProduct(product, 'order_id') - expect(result.unique_id).toEqual('order_id_random_id') - }) - - it('should not set unique_id if product_id, sku and id are not present', () => { - const product: Product = { - name: 'name', - quantity: 1, - price: 10, - category: 'category', - url: 'url', - image_url: 'image_url', - 'property with space': 'space', - property_with_underscore: 'underscore' - } - - const result = formatOrderedProduct(product, 'order_id') - expect(result.unique_id).not.toBeDefined() - }) -}) - -describe('convertKeysToTitleCase', () => { - it('should convert keys to title case upt to 2 levels', () => { - const obj = { - key1: 'value1', - key2: { - key3: 'value3', - key4: { - key5: 'value5' - } - }, - key6: ['value6', 'value7'] - } - - expect(convertKeysToTitleCase(obj)).toEqual({ - Key1: 'value1', - Key2: { - Key3: 'value3', - Key4: { - key5: 'value5' - } - }, - Key6: ['value6', 'value7'] - }) - }) -}) diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts index 61eeeb5d60..af7dc48f7e 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/__tests__/index.test.ts @@ -2,7 +2,6 @@ import nock from 'nock' import { IntegrationError, createTestEvent, createTestIntegration } from '@segment/actions-core' import Definition from '../../index' import { API_URL } from '../../config' -import { convertKeysToTitleCase } from '../formatters' const testDestination = createTestIntegration(Definition) const apiKey = 'fake-api-key' @@ -24,15 +23,13 @@ const createRequestBody = ( properties: Record, value: number, metricName: string, - profile: Record, - unique_id?: string + profile: Record ) => ({ data: { type: 'event', attributes: { properties, value, - unique_id, metric: createMetric(metricName), profile: { data: { @@ -44,42 +41,6 @@ const createRequestBody = ( } }) -function generateOrderedProductEvent( - value: number, - profile: { phone_number: string; email: string }, - properties: Record, - uniqueId?: string -) { - const productProps = { - OrderId: '1123', - ProductId: '507f1f77bcf86cd799439011', - SKU: '45790-32', - ProductName: 'Monopoly: 3rd Edition', - Categories: ['Board Games'], - Quantity: 1, - ProductURL: 'https://www.example.com/product/path', - ImageURL: 'https:///www.example.com/product/path.jpg', - ...properties - } - return { - data: { - type: 'event', - attributes: { - properties: productProps, - unique_id: `${uniqueId ?? productProps.OrderId}_${productProps.ProductId}`, - value: value, - metric: createMetric('Ordered Product'), - profile: { - data: { - type: 'profile', - attributes: profile - } - } - } - } - } -} - describe('Order Completed', () => { it('should throw error if no profile identifiers are provided', async () => { const event = createTestEvent({ type: 'track' }) @@ -96,7 +57,7 @@ describe('Order Completed', () => { const metricName = 'Order Completed' const value = 10 - const requestBody = createRequestBody(convertKeysToTitleCase(properties), value, metricName, profile) + const requestBody = createRequestBody(properties, value, metricName, profile) nock(`${API_URL}`).post('/events/', requestBody).reply(200, {}) @@ -116,7 +77,7 @@ describe('Order Completed', () => { const metricName = 'Order Completed' const value = 10 - const requestBody = createRequestBody(convertKeysToTitleCase(properties), value, metricName, profile) + const requestBody = createRequestBody(properties, value, metricName, profile) nock(`${API_URL}`).post('/events/', requestBody).reply(200, {}) @@ -136,7 +97,7 @@ describe('Order Completed', () => { const metricName = 'Order Completed' const value = 10 - const requestBody = createRequestBody(convertKeysToTitleCase(properties), value, metricName, profile) + const requestBody = createRequestBody(properties, value, metricName, profile) nock(`${API_URL}`).post('/events/', requestBody).reply(200, {}) @@ -156,7 +117,7 @@ describe('Order Completed', () => { const metricName = 'Order Completed' const value = 10 - const requestBody = createRequestBody(convertKeysToTitleCase(properties), value, metricName, profile) + const requestBody = createRequestBody(properties, value, metricName, profile) nock(`${API_URL}`).post('/events/', requestBody).reply(500, {}) @@ -175,166 +136,52 @@ describe('Order Completed', () => { it('should successfully track event with products array', async () => { const products = [ { - image_url: 'https:///www.example.com/product/path.jpg', - price: 19, - name: 'Monopoly: 3rd Edition', - quantity: 1, - sku: '45790-32', - product_id: '507f1f77bcf86cd799439011', - url: 'https://www.example.com/product/path' + value: 10, + properties: { productKey: 'productValue' } } ] const profile = { email: 'test@example.com', phone_number: '1234567890' } - const properties = { order_id: '1123' } - const expectedEventProperties = { - OrderId: '1123', - Categories: [], - ItemNames: ['Monopoly: 3rd Edition'], - Items: [ - { - ProductName: 'Monopoly: 3rd Edition', - Categories: [], - SKU: '45790-32', - ItemPrice: 19, - RowTotal: 19, - ImageURL: 'https:///www.example.com/product/path.jpg', - ProductURL: 'https://www.example.com/product/path', - Quantity: 1, - ProductId: '507f1f77bcf86cd799439011' - } - ] - } - const metricName = 'Order Completed' - const value = 19 - - const event = createTestEvent({ - type: 'track', - timestamp: '2022-01-01T00:00:00.000Z' - }) - - const mapping = { - profile, - metric_name: metricName, - properties, - value, - products: products - } - - const orderCompletedEvent = createRequestBody(expectedEventProperties, value, metricName, profile) - - nock(`${API_URL}`).post(`/events/`, orderCompletedEvent).reply(202, {}) - - const orderedProductEvent = generateOrderedProductEvent(value, profile, { - Categories: [] - }) - - nock(`${API_URL}`).post(`/events/`, orderedProductEvent).reply(200, {}) - - await expect(testDestination.testAction(`orderCompleted`, { event, mapping, settings })).resolves.not.toThrowError() - }) - - it('should add Items, ItemNames and Categories summary at the top level of Ordered Product Event', async () => { - const products = [ - { - image_url: 'https:///www.example.com/product/path.jpg', - price: 19, - name: 'Monopoly: 3rd Edition', - quantity: 1, - sku: '45790-32', - product_id: '507f1f77bcf86cd799439011', - url: 'https://www.example.com/product/path', - category: 'Board Games' - }, - { - image_url: 'https:///www.example.com/product/path.jpg', - price: 19, - name: 'Uno: 3rd Edition', - quantity: 1, - sku: '45790-32', - product_id: '1507f1f77bcf86cd799439011', - url: 'https://www.example.com/product/path', - category: 'Card Games' - } - ] - - const profile = { email: 'test@example.com', phone_number: '1234567890' } - const properties = { order_id: '1123' } - const expectedEventProperties = { - OrderId: '1123', - Categories: ['Board Games', 'Card Games'], - ItemNames: ['Monopoly: 3rd Edition', 'Uno: 3rd Edition'], - Items: [ - { - ProductName: 'Monopoly: 3rd Edition', - Categories: ['Board Games'], - SKU: '45790-32', - ItemPrice: 19, - RowTotal: 19, - ImageURL: 'https:///www.example.com/product/path.jpg', - ProductURL: 'https://www.example.com/product/path', - Quantity: 1, - ProductId: '507f1f77bcf86cd799439011' - }, - { - ProductName: 'Uno: 3rd Edition', - SKU: '45790-32', - Categories: ['Card Games'], - ItemPrice: 19, - RowTotal: 19, - ImageURL: 'https:///www.example.com/product/path.jpg', - ProductURL: 'https://www.example.com/product/path', - Quantity: 1, - ProductId: '1507f1f77bcf86cd799439011' - } - ] - } + const properties = { key: 'value' } const metricName = 'Order Completed' - const value = 19 + const value = 10 const event = createTestEvent({ type: 'track', timestamp: '2022-01-01T00:00:00.000Z' }) - const uniqueId = 'unique_id' const mapping = { profile, metric_name: metricName, properties, - unique_id: uniqueId, value, products: products } - const orderCompletedEvent = createRequestBody(expectedEventProperties, value, metricName, profile, uniqueId) - - nock(`${API_URL}`).post(`/events/`, orderCompletedEvent).reply(202, {}) - - const orderedProductEvent1 = generateOrderedProductEvent( - value, - profile, - { - ProductName: 'Monopoly: 3rd Edition', - Categories: ['Board Games'], - ProductId: '507f1f77bcf86cd799439011' - }, - uniqueId - ) - - const orderedProductEvent2 = generateOrderedProductEvent( - value, - profile, - { - ProductName: 'Uno: 3rd Edition', - Categories: ['Card Games'], - ProductId: '1507f1f77bcf86cd799439011' - }, - uniqueId - ) - - nock(`${API_URL}`).post(`/events/`, orderedProductEvent1).reply(202, {}) - nock(`${API_URL}`).post(`/events/`, orderedProductEvent2).reply(202, {}) + const requestBodyForEvent = createRequestBody(properties, value, metricName, profile) + + nock(`${API_URL}`).post(`/events/`, requestBodyForEvent).reply(202, {}) + + nock(`${API_URL}`) + .post(`/events/`, (body) => { + // Validate that body has the correct structure using function + // Can’t use an object because unique_id is randomly generated + return ( + body.data && + body.data.type === `event` && + body.data.attributes && + body.data.attributes.properties && + typeof body.data.attributes.unique_id === `string` && + body.data.attributes.metric && + body.data.attributes.metric.data && + body.data.attributes.metric.data.type === `metric` && + body.data.attributes.metric.data.attributes && + body.data.attributes.metric.data.attributes.name === `Ordered Product` && + body.data.attributes.profile + ) + }) + .reply(200, {}) await expect(testDestination.testAction(`orderCompleted`, { event, mapping, settings })).resolves.not.toThrowError() }) diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/formatters.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/formatters.ts deleted file mode 100644 index b2dd921041..0000000000 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/formatters.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Product } from './types' - -function toTitleCase(str: string) { - return ( - str - // remove special characters and replace with space - .replace(/[^a-zA-Z0-9]/g, ' ') - // replace multiple spaces with single space - .replace(/\s+/g, ' ') - // split by space and title case each word - .trim() - .split(' ') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join('') - ) -} - -// Recursively capitalize all keys in an object up to two levels deep -// Array values are not modified. -// We do this because Klaviyo examples and classic destinations use title case for keys. -// https://developers.klaviyo.com/en/docs/guide_to_integrating_a_platform_without_a_pre_built_klaviyo_integration#placed-order -export function convertKeysToTitleCase(obj: Record, level = 0): Record { - if (level > 1) return obj - return Object.fromEntries( - Object.entries(obj).map(([key, value]) => { - if (typeof value === 'object' && !Array.isArray(value)) { - return [toTitleCase(key), convertKeysToTitleCase(value as Record, level + 1)] - } - if (Array.isArray(value)) { - return [toTitleCase(key), value] - } - return [toTitleCase(key), value] - }) - ) -} - -export function formatOrderedProduct(product: Product, order_id?: string, unique_event_id?: String) { - // unique_id should ensure retries don't result in duplicate event. hence we use product_id or sku + order_id as unique_id - const event_id = unique_event_id ?? order_id - const unique_product_id = product.product_id || product.sku || product?.id - // if unique_id is not provided, klaviyo will use timestamp for deduplication - const unique_id = event_id && unique_product_id ? `${event_id}_${unique_product_id}` : undefined - - const { name, quantity, sku, price, url, image_url, category, product_id, id, ...otherProperties } = product - const productProperties = { - OrderId: order_id, - ProductId: product_id ?? id, - SKU: sku, - ProductName: name, - Quantity: quantity, - Categories: category ? [category] : [], - ProductURL: url, - ImageURL: image_url, - // copy other properties as is. If other properties have properties with same name - // as the above, they will be overwritten which is the expected behavior - ...convertKeysToTitleCase(otherProperties) - } - return { unique_id, productProperties } -} - -export function formatProductItems(product: Product) { - const { sku, name, quantity, price, category, url, image_url, id, product_id, ...otherProperties } = product - return { - ProductId: product_id ?? id, - SKU: sku, - ProductName: name, - Quantity: quantity, - ItemPrice: price, - RowTotal: price, - Categories: category ? [category] : [], - ProductURL: url, - ImageURL: image_url, - // copy other properties as is. If other properties have properties with same name - // as the above, they will be overwritten which is the expected behavior - ...convertKeysToTitleCase(otherProperties) - } -} diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/generated-types.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/generated-types.ts index 84c1b7ac03..cf8e161a4b 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/generated-types.ts +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/generated-types.ts @@ -18,13 +18,9 @@ export interface Payload { [k: string]: unknown } /** - * Properties of this event. Segment adds products array as Items, ItemNames and Categories properties in the properties object. Segment will title case the keys of this object before sending it to Klaviyo. + * Properties of this event. */ properties: { - /** - * Unique identifier for the order. - */ - order_id?: string [k: string]: unknown } /** @@ -50,38 +46,6 @@ export interface Payload { * List of products purchased in the order. */ products?: { - /** - * Id of the product. - */ - product_id?: string - /** - * Category of the product - */ - category?: string - /** - * Name of the product - */ - name?: string - /** - * Stock Keeping Unit of the product - */ - sku?: string - /** - * Price of the product - */ - price?: number - /** - * URL of the image of the product - */ - image_url?: string - /** - * URL of the product page - */ - url?: string - /** - * Quantity of the product - */ - quantity?: number [k: string]: unknown }[] } diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts index c518d04783..ace06f76bb 100644 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/index.ts @@ -3,13 +3,75 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import { PayloadValidationError, RequestClient } from '@segment/actions-core' import { API_URL } from '../config' -import { convertKeysToTitleCase, formatOrderedProduct, formatProductItems } from './formatters' -import { Product } from './types' +import { EventData } from '../types' +import { v4 as uuidv4 } from '@lukeed/uuid' + +const createEventData = (payload: Payload) => ({ + data: { + type: 'event', + attributes: { + properties: { ...payload.properties }, + time: payload.time, + value: payload.value, + unique_id: payload.unique_id, + metric: { + data: { + type: 'metric', + attributes: { + name: 'Order Completed' + } + } + }, + profile: { + data: { + type: 'profile', + attributes: payload.profile + } + } + } + } +}) + +const sendProductRequests = async (payload: Payload, orderEventData: EventData, request: RequestClient) => { + if (!payload.products || !Array.isArray(payload.products)) { + return + } + + delete orderEventData.data.attributes.properties?.products + const productPromises = payload.products.map((product) => { + const productEventData = { + data: { + type: 'event', + attributes: { + properties: { ...product, ...orderEventData.data.attributes.properties }, + unique_id: uuidv4(), + metric: { + data: { + type: 'metric', + attributes: { + name: 'Ordered Product' + } + } + }, + time: orderEventData.data.attributes.time, + profile: orderEventData.data.attributes.profile + } + } + } + + return request(`${API_URL}/events/`, { + method: 'POST', + json: productEventData + }) + }) + + await Promise.all(productPromises) +} const action: ActionDefinition = { title: 'Order Completed', description: 'Order Completed Event action tracks users Order Completed events and associate it with their profile.', - defaultSubscription: 'type = "track" and event = "Order Completed"', + defaultSubscription: 'type = "track"', fields: { profile: { label: 'Profile', @@ -42,30 +104,11 @@ const action: ActionDefinition = { required: true }, properties: { - description: `Properties of this event. Segment adds products array as Items, ItemNames and Categories properties in the properties object. Segment will title case the keys of this object before sending it to Klaviyo.`, + description: `Properties of this event.`, label: 'Properties', type: 'object', - additionalProperties: true, - properties: { - order_id: { - label: 'Order ID', - description: 'Unique identifier for the order.', - type: 'string' - } - }, default: { - '@arrayPath': [ - '$.properties', - { - order_id: { - '@if': { - exists: { '@path': '$.properties.order_id' }, - then: { '@path': '$.properties.order_id' }, - else: { '@path': '$.properties.orderId' } - } - } - } - ] + '@path': '$.properties' }, required: true }, @@ -101,71 +144,7 @@ const action: ActionDefinition = { label: 'Products', description: 'List of products purchased in the order.', multiple: true, - type: 'object', - additionalProperties: true, - properties: { - product_id: { - label: 'Product ID', - description: 'Id of the product.', - type: 'string' - }, - category: { - label: 'Category', - description: 'Category of the product', - type: 'string' - }, - name: { - label: 'Name', - type: 'string', - description: 'Name of the product' - }, - sku: { - label: 'SKU', - type: 'string', - description: 'Stock Keeping Unit of the product' - }, - price: { - label: 'Price', - type: 'number', - description: 'Price of the product' - }, - image_url: { - label: 'Image URL of the product', - type: 'string', - description: 'URL of the image of the product' - }, - url: { - label: 'Product URL', - type: 'string', - description: 'URL of the product page' - }, - quantity: { - label: 'Quantity', - type: 'number', - description: 'Quantity of the product' - } - }, - default: { - '@arrayPath': [ - '$.properties.products', - { - product_id: { - '@if': { - exists: { '@path': '$.properties.product_id' }, - then: { '@path': '$.properties.product_id' }, - else: { '@path': '$.properties.productId' } - } - }, - category: { '@path': '$.properties.category' }, - name: { '@path': '$.properties.name' }, - sku: { '@path': '$.properties.sku' }, - price: { '@path': '$.properties.price' }, - image_url: { '@path': '$.properties.image_url' }, - url: { '@path': '$.properties.url' }, - quantity: { '@path': '$.properties.quantity' } - } - ] - } + type: 'object' } }, @@ -176,108 +155,18 @@ const action: ActionDefinition = { throw new PayloadValidationError('One of External ID, Anonymous ID, Phone Number or Email is required.') } - const eventData = createOrderCompleteEvent(payload) + const eventData = createEventData(payload) + const event = await request(`${API_URL}/events/`, { method: 'POST', json: eventData }) if (event.status == 202 && Array.isArray(payload.products)) { - await sendProductRequests(payload, request) + await sendProductRequests(payload, eventData, request) } return event } } -function createOrderCompleteEvent(payload: Payload) { - // products is generally an array part of the properties object. - // so we get rid of it as we already map it to payload.products - delete payload.properties?.products - const categories = payload.products?.filter((product) => Boolean(product.category)).map((product) => product.category) - const itemNames = payload.products?.filter((product) => Boolean(product.name)).map((product) => product.name) - - const items = payload.products?.map(formatProductItems) - - return { - data: { - type: 'event', - attributes: { - properties: { - Categories: categories, - ItemNames: itemNames, - // products array is reformatted and sent as items - Items: items, - ...convertKeysToTitleCase(payload.properties) - }, - time: payload.time, - value: payload.value, - unique_id: payload.unique_id, - metric: { - data: { - type: 'metric', - attributes: { - name: 'Order Completed' - } - } - }, - profile: { - data: { - type: 'profile', - attributes: payload.profile - } - } - } - } - } -} - -function sendOrderedProduct(request: RequestClient, payload: Payload, product: Product) { - const { unique_id, productProperties } = formatOrderedProduct(product, payload.properties.order_id, payload.unique_id) - - const productEventData = { - data: { - type: 'event', - attributes: { - properties: productProperties, - unique_id: unique_id, - // for ordered product, we use price as value - value: product.price, - metric: { - data: { - type: 'metric', - attributes: { - name: 'Ordered Product' - } - } - }, - time: payload.time, - profile: { - data: { - type: 'profile', - attributes: payload.profile - } - } - } - } - } - - return request(`${API_URL}/events/`, { - method: 'POST', - json: productEventData - }) -} - -const sendProductRequests = async (payload: Payload, request: RequestClient) => { - if (!Array.isArray(payload.products)) { - return - } - - // products is generally an array part of the properties object. - // so we get rid of it as we already map it to payload.products - delete payload.properties?.products - - const productPromises = payload.products.map((product) => sendOrderedProduct(request, payload, product)) - await Promise.all(productPromises) -} - export default action diff --git a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/types.ts b/packages/destination-actions/src/destinations/klaviyo/orderCompleted/types.ts deleted file mode 100644 index d9e04fd0c9..0000000000 --- a/packages/destination-actions/src/destinations/klaviyo/orderCompleted/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Payload } from './generated-types' - -export type Product = NonNullable[number] diff --git a/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts b/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts index 7b87dc23ba..83658e381f 100644 --- a/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/klaviyo/trackEvent/index.ts @@ -8,7 +8,7 @@ import { API_URL } from '../config' const action: ActionDefinition = { title: 'Track Event', description: 'Track user events and associate it with their profile.', - defaultSubscription: 'type = "track" and event != "Order Completed"', + defaultSubscription: 'type = "track"', fields: { profile: { label: 'Profile',