Skip to content

Commit

Permalink
Merge pull request #7 from managed-components/ecommerce
Browse files Browse the repository at this point in the history
add ecommerce and clean manager event listeners
  • Loading branch information
jonnyparris authored Jan 30, 2024
2 parents 07089a4 + fbd0e8e commit 4147f9a
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 71 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,46 @@ Find out more about Managed Components [here](https://blog.cloudflare.com/zaraz-

`tid` Pixel Tag ID - The Pinterest Tag ID is the unique identifier of your Pinterest tag. [Learn more](https://help.pinterest.com/en/business/article/track-conversions-with-pinterest-tag).

---

## Fields Description

Fields are properties that can/must be sent with certain events.

### Required Fields

#### Event Name `string` _required_

`event` - The name of the tracking event to be sent to Pinterest.

### Optional Fields

#### User Defined Event `string` _optional_

`ude` - Specify a custom event name for audience targeting purposes. Spaces in the event name will be removed. [Learn more](https://help.pinterest.com/en/business/article/add-event-codes).

#### Partner Data Email `string` _optional_

`pdem` - Specifies the email address associated with the partner data, if applicable.

#### Tag Manager `string` _optional_

`tm` - Indicates the Tag Manager used, defaults to 'pinterest-mc' if not specified.

#### Lead Type `string` _optional_

`lead_type` - Describes the type of lead being tracked, such as 'Newsletter', 'Signup', etc.

#### Video Title `string` _optional_

`video_title` - The title of the video for tracking specific video events.

#### E-commerce Tracking `boolean` _optional_

`ecommerce` - Enables or disables the forwarding of Zaraz E-commerce API events to Pinterest. This includes events like Search, AddToCart, and Checkout. [Learn more](https://help.pinterest.com/en-gb/business/article/add-event-codes).

---

## 📝 License

Licensed under the [Apache License](./LICENSE).
Expand Down
Binary file removed assets/pinterest.png
Binary file not shown.
15 changes: 15 additions & 0 deletions assets/pinterest.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Pinterest",
"namespace": "pinterest",
"description": "Pinterest Managed Component",
"icon": "assets/icon.svg",
"icon": "assets/pinterest.svg",
"categories": ["Analytics"],
"provides": ["events"],
"allowCustomFields": true,
Expand Down
42 changes: 14 additions & 28 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,20 @@ describe('Pinterest MC sends correct request', () => {
const baseHost = `${baseHostname}:${port}`
const baseOrigin = `https://${baseHost}`
const baseHref = `${baseOrigin}/`
const searchParams = new URLSearchParams()

const mockEvent = new Event('pagevisit', {}) as MCEvent
mockEvent.name = 'Pinterest Test'
mockEvent.payload = { timestamp: 1670409810, event: 'pagevisit', tid: 'xyz' }
mockEvent.client = {
url: {
href: baseHref,
origin: baseOrigin,
protocol: 'http:',
username: '',
password: '',
host: baseHost,
hostname: baseHostname,
port: port,
pathname: '/',
search: '',
searchParams: searchParams,
hash: '',
const mockEvent: MCEvent = {
payload: { timestamp: 1670409810, event: 'pageview', tid: 'xyz' },
client: {
url: new URL(baseHref),
title: 'Zaraz "Test" /t Page',
timestamp: 1670409810,
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
language: 'en-GB',
referer: `${baseOrigin}/somewhere-else.html`,
ip: baseHostname,
emitter: 'browser',
},
title: 'Zaraz "Test" /t Page',
timestamp: 1670409810,
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
language: 'en-GB',
referer: `${baseOrigin}/somewhere-else.html`,
ip: baseHostname,
emitter: 'browser',
}

const settings: ComponentSettings = {}
Expand Down Expand Up @@ -83,7 +69,7 @@ describe('Pinterest MC sends correct request', () => {
'pd[tm]': 'pinterest-mc',
ed: '{"timestamp":1670409810,"event":"pagevisit"}',
}
const requestUrl = getRequestUrl(rawRequestBody, mockEvent, settings)
const requestUrl = getRequestUrl(rawRequestBody)
const requestUrlDecoded = decodeURI(requestUrl)

const expectedUrl = `https://ct.pinterest.com/v3/?ad={"loc"%3A"https%3A%2F%2F127.0.0.1%3A1337%2F"%2C"ref"%3A"https%3A%2F%2F127.0.0.1%3A1337%2Fsomewhere-else.html"%2C"if"%3Afalse%2C"mh"%3A"2424edb5"}&cb=1671006315874&tid=xyz&event=pagevisit&pd[tm]=pinterest-mc&ed={"timestamp"%3A1670409810%2C"event"%3A"pagevisit"}`
Expand All @@ -93,7 +79,7 @@ describe('Pinterest MC sends correct request', () => {

it('Handler invokes fetch correctly', () => {
const arr = []
handler('pageview', mockEvent, settings, (...args) => {
handler(mockEvent, settings, 'pageview', (...args) => {
arr.push(args)
})
expect(arr.length).toBe(1)
Expand Down
152 changes: 110 additions & 42 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,82 @@ export type RequestBodyType = {
ed?: string
}

type EcommerceType = {
order_id: number | string
currency: string
revenue: number | string
total: number | string
value: number | string
quantity: number | string
products: Product[] | null
checkout_id: number | string
affiliation: string
shipping: number | string
tax: number | string
discount: number | string
coupon: string
creative: string
query: string
step: number | string
payment_type: string
}

export type Product = {
product_id: number | string
sku: number | string
name: string
category: string
brand: string
price: number | string
quantity: number
variant: string
currency: string
value: number | string
position: number | string
coupon: number | string
}
const eventMappings: { [key: string]: string } = {
'Product Added': 'addtocart',
'Order Completed': 'checkout',
'Products Searched': 'search',
}
function mapEcommerceEvent(eventName: string): string | undefined {
return eventMappings[eventName] || undefined
}
function mapEcommerceData(
ecommerce: EcommerceType
): Record<string, string | number> | null {
const transformedProductData: Record<string, string | number> = {}
if (!ecommerce) {
return null
} else {
ecommerce.products?.forEach((product, index) =>
[
'product_id',
'sku',
'category',
'name',
'brand',
'variant',
'price',
].forEach(prop => {
const key = `product_${prop}[${index}]`
transformedProductData[key] =
product[prop as keyof Product] || product.sku
})
)
}

const ecommerceData = {
order_id: ecommerce.order_id,
currency: ecommerce.currency,
value: ecommerce.revenue || ecommerce.total || ecommerce.value,
order_quantity: ecommerce.quantity,
...transformedProductData,
}
return ecommerceData
}

export const getRequestBody = (
eventType: string,
event: MCEvent,
Expand All @@ -44,20 +120,25 @@ export const getRequestBody = (
event: eventType,
}

const { 'pd[em]': pdem, tid, ...cleanPayload } = payload

const { pdem, tid, ecommerce, ...cleanPayload } = payload
// pd - partner data
if (pdem) {
requestBody['pd[em]'] = pdem
}

requestBody['pd[tm]'] = payload.tm || 'pinterest-mc'

// match event types to Pinterest's default

const ecommerceData = mapEcommerceData(ecommerce)
for (const key in ecommerceData) {
cleanPayload[key] = ecommerceData[key]
}

if (Object.keys(cleanPayload).length) {
// event data
// event data is created, note that it also holds the ecommerce parameters
requestBody['ed'] = JSON.stringify(cleanPayload)
}

return requestBody
}

Expand All @@ -75,54 +156,41 @@ export const sendRequest = (url: string, event: MCEvent) => {
}

export const handler = (
eventType: string,
event: MCEvent,
settings: ComponentSettings,
ev: string,
customSendRequest = sendRequest
) => {
const eventType = event.payload.ude || ev // ude is the "user defined event" field in case of eventType ='user-defined-event'
const requestBody = getRequestBody(eventType, event, settings)
const requestUrl = getRequestUrl(requestBody)
customSendRequest(requestUrl, event)
}

export default async function (manager: Manager, settings: ComponentSettings) {
manager.addEventListener('pageview', event => {
handler('pagevisit', event, settings)
const events = [
'pageview',
'lead',
'signup',
'watchvideo',
'viewcategory',
'custom',
'addtocart',
'checkout',
'search',
'user-defined-event',
]
events.forEach(ev => {
manager.addEventListener(ev, event => {
handler(event, settings, ev)
})
})

manager.addEventListener('addtocart', event => {
handler('addtocart', event, settings)
})

manager.addEventListener('checkout', event => {
handler('checkout', event, settings)
})

manager.addEventListener('lead', event => {
handler('lead', event, settings)
})

manager.addEventListener('signup', event => {
handler('signup', event, settings)
})

manager.addEventListener('viewcategory', event => {
handler('viewcategory', event, settings)
})
manager.addEventListener('watchvideo', event => {
handler('watchVideo', event, settings)
})

manager.addEventListener('custom', event => {
handler('custom', event, settings)
})

manager.addEventListener('search', event => {
handler('search', event, settings)
})

manager.addEventListener('userdefinedevent', event => {
const userDefinedEvent: string = event.payload.userDefinedEvent
handler(userDefinedEvent, event, settings)
manager.addEventListener('ecommerce', event => {
if (typeof event.name === 'string') {
const ev = mapEcommerceEvent(event.name)
if (ev) {
handler(event, settings, ev)
}
}
})
}

0 comments on commit 4147f9a

Please sign in to comment.