Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tracker): add currency context, add transactionTotal to order tr… #1078

Merged
merged 3 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions docs/INTEGRATION_CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ There are a few core context variables utilized by Snap, `shopper`, `merchandisi
|---|---|:---:|---|
| shopper.id | logged in user unique identifier | all | required for personalization functionallity |
| shopper.cart | array of cart objects, each object in the array can contain `sku` and/or `childSku` | all | current cart contents, required if checkout process does not contain a dedicated cart page (ie. slideout cart) |
| currency.code | currency code string, ie. 'EUR' (ISO 4217) | all | currency code of the shopper's cart contents or order confirmation. Used for beacon events containing pricing data |
| merchandising.segments | array of strings used for merchandising | any | segmented merchandising allows for custom control over products returned on search requests and must also be setup within the Searchspring Management Console (SMC) |
| config | object containing Snap configurations | any | advanced usage of Snap (not recommended for standard integrations) |

## Examples

The custom variable example below shows a custom context being added for 'region'. The value would typically be assigned server side using template logic. This would be used to possibly toggle the siteId utilized by the client (to fetch different catalog data) or to modify text or currency displays.
The custom variable example below shows a custom context being added for 'page'. The value would typically be assigned server side using template logic. This would be used to possibly toggle the siteId utilized by the client (to fetch different catalog data) or to modify text or currency displays.

```html
<script src="https://snapui.searchspring.io/[your_site_id]/bundle.js">
region = "US";
page = "404";
</script>
```

Expand All @@ -36,6 +37,9 @@ When used, shopper context should always include at least an `id`; the `cart` co
}
]
};
currency = {
code: 'EUR'
};
</script>
```

Expand All @@ -53,12 +57,15 @@ Example using multiple context variables together.

```html
<script src="https://snapui.searchspring.io/[your_site_id]/bundle.js">
region = "CAD";
page = "404";
shopper = {
id: '[email protected]'
};
merchandising = {
segments: ['country:canada']
};
currency = {
code: 'EUR'
};
</script>
```
13 changes: 11 additions & 2 deletions packages/snap-preact/src/Snap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export class Snap {
let globalContext: ContextVariables = {};
try {
// get global context
globalContext = getContext(['shopper', 'config', 'merchandising', 'siteId']);
globalContext = getContext(['shopper', 'config', 'merchandising', 'siteId', 'currency']);
} catch (err) {
console.error('Snap failed to find global context');
}
Expand Down Expand Up @@ -394,7 +394,16 @@ export class Snap {
this.logger = services?.logger || new Logger({ prefix: 'Snap Preact ', mode: this.mode });

// create tracker
const trackerGlobals = this.config.tracker?.globals || (this.config.client!.globals as ClientGlobals);
let trackerGlobals = this.config.tracker?.globals || (this.config.client!.globals as ClientGlobals);

if (this.context.currency?.code) {
trackerGlobals = deepmerge(trackerGlobals || {}, {
currency: {
code: this.context.currency.code,
},
dkonieczek marked this conversation as resolved.
Show resolved Hide resolved
});
}

const trackerConfig = deepmerge(this.config.tracker?.config || {}, { framework: 'preact', mode: this.mode });
this.tracker = services?.tracker || new Tracker(trackerGlobals, trackerConfig);

Expand Down
8 changes: 4 additions & 4 deletions packages/snap-tracker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ tracker.track.order.transaction({
order: {
id: '123456',
total: '34.29',
transactionTotal: '31.97',
transactionTotal: '31.97',
city: 'Los Angeles',
state: 'CA',
country: 'US',
Expand All @@ -359,10 +359,10 @@ tracker.track.order.transaction({
## Tracker properties

### `globals` property
When constructing an instance of `Tracker`, a globals object is required to be constructed. This object contains a `siteId` key and value. An optional `currency` key and value can be provided.
When constructing an instance of `Tracker`, a globals object is required to be constructed. This object contains a `siteId` key and value. An optional `currency` object with a `code` property containing a string can be provided.

```typescript
const globals = { siteId: 'abc123' };
const globals = { siteId: 'abc123', currency: { code: 'EUR' } };
const tracker = new Tracker(globals);
console.log(tracker.globals === globals) // true
```
Expand Down Expand Up @@ -422,7 +422,7 @@ Sets the currency code on the tracker context.
```typescript
const tracker = new Tracker();

tracker.setCurrency('EUR')
tracker.setCurrency({ code: 'EUR' })
```


Expand Down
5 changes: 4 additions & 1 deletion packages/snap-tracker/src/PixelEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ export class PixelEvent {
this.src =
this.endpoint +
`?s=${encodeURIComponent(payload?.context?.website?.trackingCode || '')}` +
`&currencyCode=${encodeURIComponent(payload?.context?.currency?.code || '')}` +
`&u=${encodeURIComponent(payload?.context?.userId || '')}` +
`&ce=${featureFlags.cookies ? '1' : '0'}` +
`&pt=${encodeURIComponent(document.title)}` +
`&v=1` + // version always '1'? or set to snap package version?
`&x=${Math.floor(Math.random() * 2147483647)}` +
`${window.document.referrer ? `&r=${encodeURIComponent(window.document.referrer)}` : ''}`;

if (payload?.context?.currency?.code) {
this.src += `&currencyCode=${encodeURIComponent(payload?.context?.currency?.code)}`;
}

switch (payload.category) {
case BeaconCategory.PAGEVIEW:
this.event = payload.event as ProductViewEvent;
Expand Down
27 changes: 10 additions & 17 deletions packages/snap-tracker/src/Tracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('Script Block Tracking', () => {
const trackEvent = jest.spyOn(tracker.track.cart, 'view');

await new Promise((r) => setTimeout(r));
expect(trackEvent).toHaveBeenCalledWith({ items }, undefined, undefined);
expect(trackEvent).toHaveBeenCalledWith({ items }, undefined);

trackEvent.mockRestore();
});
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('Script Block Tracking', () => {
const trackEvent = jest.spyOn(tracker.track.order, 'transaction');

await new Promise((r) => setTimeout(r));
expect(trackEvent).toHaveBeenCalledWith({ order, items }, undefined, undefined);
expect(trackEvent).toHaveBeenCalledWith({ order, items }, undefined);

trackEvent.mockRestore();
});
Expand Down Expand Up @@ -480,7 +480,7 @@ describe('Tracker', () => {

const customGlobals = {
siteId: 'custom',
currency: 'EUR',
currency: { code: 'EUR' },
};

const tracker = new Tracker(globals);
Expand Down Expand Up @@ -518,7 +518,7 @@ describe('Tracker', () => {
expect(tracker2.context.currency).toBeDefined();

// @ts-ignore - private property
expect(tracker2.context.currency.code).toStrictEqual(customGlobals.currency);
expect(tracker2.context.currency).toStrictEqual(customGlobals.currency);
});

it('can persist userId in storage if cookies are disabled', async () => {
Expand Down Expand Up @@ -1084,13 +1084,12 @@ describe('Tracker', () => {
eventFn.mockRestore();
});

it('can invoke track.cart.view with siteId and currency override', async () => {
it('can invoke track.cart.view with siteId override', async () => {
const tracker = new Tracker(globals, config);
const trackEvent = jest.spyOn(tracker.track, 'event');
const cartView = jest.spyOn(tracker.track.cart, 'view');

const siteId = 'xxxxxx';
const currency = 'EUR';
const payload = {
items: [
{
Expand All @@ -1105,7 +1104,7 @@ describe('Tracker', () => {
},
],
};
await tracker.track.cart.view(payload, siteId, currency);
await tracker.track.cart.view(payload, siteId);

expect(trackEvent).toHaveBeenCalledWith({
type: BeaconType.CART,
Expand All @@ -1116,14 +1115,11 @@ describe('Tracker', () => {
website: {
trackingCode: siteId,
},
currency: {
code: currency,
},
},
}),
event: { ...payload },
});
expect(cartView).toHaveBeenCalledWith(payload, siteId, currency);
expect(cartView).toHaveBeenCalledWith(payload, siteId);

cartView.mockRestore();
trackEvent.mockRestore();
Expand Down Expand Up @@ -1283,7 +1279,7 @@ describe('Tracker', () => {
eventFn.mockRestore();
});

it('can invoke track.order.transaction with siteId and currency override', async () => {
it('can invoke track.order.transaction with siteId override', async () => {
const tracker = new Tracker(globals, config);
const trackEvent = jest.spyOn(tracker.track, 'event');
const orderTransaction = jest.spyOn(tracker.track.order, 'transaction');
Expand All @@ -1308,7 +1304,7 @@ describe('Tracker', () => {
},
],
};
await tracker.track.order.transaction(payload, siteId, currency);
await tracker.track.order.transaction(payload, siteId);

expect(trackEvent).toHaveBeenCalledWith({
type: BeaconType.ORDER,
Expand All @@ -1319,9 +1315,6 @@ describe('Tracker', () => {
website: {
trackingCode: siteId,
},
currency: {
code: currency,
},
},
}),
event: {
Expand All @@ -1334,7 +1327,7 @@ describe('Tracker', () => {
items: payload.items,
},
});
expect(orderTransaction).toHaveBeenCalledWith(payload, siteId, currency);
expect(orderTransaction).toHaveBeenCalledWith(payload, siteId);

orderTransaction.mockRestore();
trackEvent.mockRestore();
Expand Down
48 changes: 16 additions & 32 deletions packages/snap-tracker/src/Tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
TrackerConfig,
DoNotTrackEntry,
PreflightRequestModel,
CurrencyContext,
} from './types';

export const BATCH_TIMEOUT = 200;
Expand Down Expand Up @@ -89,9 +90,12 @@ export class Tracker {
},
};

if (this.globals.currency) {
this.context.currency = {
code: this.globals.currency,
if (this.globals.currency?.code) {
this.context = {
...this.context,
currency: {
code: this.globals.currency.code,
},
};
dkonieczek marked this conversation as resolved.
Show resolved Hide resolved
}

Expand All @@ -105,8 +109,8 @@ export class Tracker {
setTimeout(() => {
this.targeters.push(
new DomTargeter([{ selector: 'script[type^="searchspring/track/"]', emptyTarget: false }], (target: any, elem: Element) => {
const { item, items, siteId, currency, shopper, order, type } = getContext(
['item', 'items', 'siteId', 'currency', 'shopper', 'order', 'type'],
const { item, items, siteId, shopper, order, type } = getContext(
dkonieczek marked this conversation as resolved.
Show resolved Hide resolved
['item', 'items', 'siteId', 'shopper', 'order', 'type'],
elem as HTMLScriptElement
);

Expand All @@ -118,10 +122,10 @@ export class Tracker {
this.track.product.view(item, siteId);
break;
case 'searchspring/track/cart/view':
this.track.cart.view({ items }, siteId, currency);
this.track.cart.view({ items }, siteId);
break;
case 'searchspring/track/order/transaction':
this.track.order.transaction({ order, items }, siteId, currency);
this.track.order.transaction({ order, items }, siteId);
break;
default:
console.error(`event '${type}' is not supported`);
Expand Down Expand Up @@ -424,7 +428,7 @@ export class Tracker {
},
},
cart: {
view: (data: CartViewEvent, siteId?: string, currency?: string): BeaconEvent | undefined => {
view: (data: CartViewEvent, siteId?: string): BeaconEvent | undefined => {
if (!Array.isArray(data?.items) || !data?.items.length) {
console.error(
'track.view.cart event: parameter must be an array of cart items. \nExample: track.view.cart({ items: [{ sku: "product123", childSku: "product123_a", qty: "1", price: "9.99" }] })'
Expand All @@ -441,15 +445,6 @@ export class Tracker {
},
});
}
if (currency) {
context = deepmerge(context, {
context: {
currency: {
code: currency,
},
},
});
}
const items = data.items.map((item, index) => {
if (!item?.qty || !item?.price || (!item?.sku && !item?.childSku)) {
console.error(
Expand Down Expand Up @@ -490,7 +485,7 @@ export class Tracker {
},
},
order: {
transaction: (data: OrderTransactionData, siteId?: string, currency?: string): BeaconEvent | undefined => {
transaction: (data: OrderTransactionData, siteId?: string): BeaconEvent | undefined => {
if (!data?.items || !Array.isArray(data.items) || !data.items.length) {
console.error(
'track.order.transaction event: object parameter must contain `items` array of cart items. \nExample: order.transaction({ order: { id: "1001", total: "10.71", transactionTotal: "9.99", city: "Los Angeles", state: "CA", country: "US" }, items: [{ sku: "product123", childSku: "product123_a", qty: "1", price: "9.99" }] })'
Expand All @@ -507,15 +502,6 @@ export class Tracker {
},
});
}
if (currency) {
context = deepmerge(context, {
context: {
currency: {
code: currency,
},
},
});
}
const items = data.items.map((item, index) => {
if (!item?.qty || !item?.price || (!item?.sku && !item?.childSku)) {
console.error(
Expand Down Expand Up @@ -571,13 +557,11 @@ export class Tracker {
}
};

setCurrency = (currency: string): void => {
if (!currency) {
setCurrency = (currency: CurrencyContext): void => {
if (!currency?.code) {
return;
}
this.context.currency = {
code: currency,
};
this.context.currency = currency;
};

getUserId = (): string | undefined | null => {
Expand Down
14 changes: 8 additions & 6 deletions packages/snap-tracker/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { AppMode } from '@searchspring/snap-toolbox';
import { BeaconEvent } from './BeaconEvent';

export type CurrencyContext = {
code: string;
};

export type TrackerGlobals = {
siteId: string;
currency?: string;
currency?: CurrencyContext;
};

export type DoNotTrackEntry = {
Expand Down Expand Up @@ -98,9 +102,7 @@ export interface BeaconContext {
type?: string;
id?: string;
};
currency?: {
code: string;
};
currency?: CurrencyContext;
}

export interface BeaconMeta {
Expand Down Expand Up @@ -220,10 +222,10 @@ export interface TrackMethods {
click: (data: ProductClickEvent, siteId?: string) => BeaconEvent | undefined;
};
cart: {
view: (data: CartViewEvent, siteId?: string, currency?: string) => BeaconEvent | undefined;
view: (data: CartViewEvent, siteId?: string) => BeaconEvent | undefined;
};
order: {
transaction: (data: OrderTransactionData, siteId?: string, currency?: string) => BeaconEvent | undefined;
transaction: (data: OrderTransactionData, siteId?: string) => BeaconEvent | undefined;
};
}

Expand Down
Loading