Skip to content

Commit

Permalink
implements conversion tracking for pages
Browse files Browse the repository at this point in the history
  • Loading branch information
benmcginnis committed Aug 1, 2022
1 parent 675f859 commit 383ab4b
Show file tree
Hide file tree
Showing 18 changed files with 290 additions and 115 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,45 @@ We can also fire an event on any other type of user interaction and give it a cu
pagesAnalytics.track({eventType: 'C_MY_CUSTOM_EVENT'});
```

### Conversion Tracking

Yext offers conversion tracking that can attribute values to conversion events that are driven by user interaction
with Yext's products. Once you have [setup conversion tracking](https://hitchhikers.yext.com/modules/ana104-conversion/01-conversion-overview/)
you can create a conversionTracking provider like so:

```ts
import { provideConversionTrackingAnalytics } from '@yext/analytics';
const conversionTracker = provideConversionTrackingAnalytics();
```

In order to track conversions, you will need to set a Cookie on your users and pass the id of that cookie to the
conversion tracker when a conversion event occurs. Which can be done like so:

```ts
conversionTracker.trackConversion({
cookieId: 'the unique id that you generated for the user cookie,
cid: 'the value of the tag found in the conversion tracking setup page in your account',
cv: 10, // the optional monetary value of the conversion event.
})
```

Additionally, if you are implementing Conversion tracking on a pages site, once you have setup the pages analytics
tracker, you should turn on conversion tracking so that interactions on your pages site can be properly credited with
conversions. That can be done like so:

```ts
pagesAnalytics.setConversionTrackingEnabled(true, 'cookie id of the user goes here');
```

Then, when you track a page view it will automatically be credited for conversion tracking purposes. Additionally, if
an event on your pages should be treated as a conversion, you would track it like so:

```ts
pagesAnalytics.track('event_to_track', {
cid: '12345-abcde-67890-fghij', // the value of the tag found in the conversion tracking setup page in your account
cv: 10, // the optional monetary value of the conversion event.
});
```

And that's it! See **[our documentation](./docs/analytics.md)** for a full list of analytics events.

Expand Down
29 changes: 15 additions & 14 deletions src/infra/ConversionTrackingReporter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CookieParam, ConversionEvent, ListingsClickEvent } from '../models';
import { ConversionEvent, COOKIE_PARAM, ListingsClickEvent } from '../models';
import { DEFAULT_CONVERSION_TRACKING_DOMAIN } from '../models/constants';
import { CommonConversionData } from '../models/conversiontracking/CommonConversionData';
import { ConversionTrackingService, HttpRequesterService } from '../services';
import { calculateSeed } from './CalculateSeed';

const domain = 'realtimeanalytics.yext.com';
const conversionEndpoint = 'conversiontracking/conversion';
const listingsEndpoint = 'listings';

Expand Down Expand Up @@ -37,33 +38,33 @@ export class ConversionTrackingReporter implements ConversionTrackingService {
}
}

private static formatBaseEvent(event: CommonConversionData, params: URLSearchParams): void {
params.set(COOKIE_PARAM, event.cookieId);
if (event.referrer) params.set('referrer', event.referrer);
params.set('v', calculateSeed().toString());
}

/** {@inheritDoc ConversionTrackingService.trackConversion} */
async trackConversion(event: ConversionEvent): Promise<void> {
const url = new URL(`https://${domain}/${conversionEndpoint}`);
const url = new URL(`https://${DEFAULT_CONVERSION_TRACKING_DOMAIN}/${conversionEndpoint}`);
const params = new URLSearchParams();
params.set('cid', event.cid);
if (event.firstPartyCookieId) params.set(CookieParam, event.firstPartyCookieId);
if (event.referrer) params.set('referrer', event.referrer);
if (event.cv) params.set('cv', event.cv);
if (event.thirdPartyCookieId) params.set('cookieId', event.thirdPartyCookieId);
params.set('v', calculateSeed().toString());
ConversionTrackingReporter.formatBaseEvent(event, params);
url.search = params.toString();
const res = await this.handleRequest(url.toString());
await this.handleRequest(url.toString());
this.printEvent(event.cid, 'Conversion');
}

/** {@inheritDoc ConversionTrackingService.trackListings} */
async trackListings(event: ListingsClickEvent): Promise<void> {
const url = new URL(`https://${domain}/${listingsEndpoint}`);
const url = new URL(`https://${DEFAULT_CONVERSION_TRACKING_DOMAIN}/${listingsEndpoint}`);
const params = new URLSearchParams();
params.set('y_source', event.source);
params.set('location', event.location);
if (event.referrer) params.set('referrer', event.referrer);
if (event.firstPartyCookieId) params.set(CookieParam, event.firstPartyCookieId);
if (event.thirdPartyCookieId) params.set('cookieId', event.thirdPartyCookieId);
params.set('v', calculateSeed().toString());
ConversionTrackingReporter.formatBaseEvent(event, params);
url.search = params.toString();
const res = await this.handleRequest(url.toString());
await this.handleRequest(url.toString());
this.printEvent(event.source, 'Listings Click');
}

Expand Down
59 changes: 52 additions & 7 deletions src/infra/PagesAnalyticsReporter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { COOKIE_PARAM, DEFAULT_CONVERSION_TRACKING_DOMAIN } from '../models/constants';
import { ConversionDetails } from '../models/conversiontracking/ConversionDetails';
import { HttpRequesterService, PagesAnalyticsService } from '../services';
import { DefaultPagesEventNames, PagesAnalyticsConfig, Visitor } from '../models';
import { PagesAnalyticsEvent } from '../models';
import { PageViewEvent } from '../models';
import { calculateSeed } from './CalculateSeed';
import { ConversionTrackingReporter } from './ConversionTrackingReporter';

const DEFAULT_DOMAIN_PAGES = 'https://www.yext-pixel.com';
const PRODUCT_NAME = 'storepages';
const DEFAULT_DOMAIN_PAGES = 'www.yext-pixel.com';
const PRODUCT_NAME = 'sites';
const ENDPOINT = 'store_pagespixel';

// TODO: Implement conversion tracking
enum urlParamNames {
BusinessId = 'businessids',
Product = 'product',
Expand All @@ -23,6 +26,8 @@ enum urlParamNames {
SearchId = 'searchId',
StaticPageId = 'staticPageId',
PageType = 'pageType',
VisitorId = 'visitorId',
VisitorMethod = 'visitorIdMethod',
}

const eventTypeNameMapping = new Map<string, string>();
Expand All @@ -48,10 +53,14 @@ function getEventName(name: string): string {
export class PagesAnalyticsReporter implements PagesAnalyticsService{
private _visitor: Visitor |undefined;
private _debug: boolean|undefined;
private _conversionTrackingEnabled: boolean|undefined;
private _cookieID: string|undefined;
private _conversionTracker: ConversionTrackingReporter;
constructor(private config: PagesAnalyticsConfig,
private httpRequesterService: HttpRequesterService) {
this.setVisitor(config.visitor);
this._debug = config.debug;
this._conversionTracker = new ConversionTrackingReporter(this.httpRequesterService, this._debug);
}

/**
Expand Down Expand Up @@ -94,7 +103,16 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{

params.set(urlParamNames.CacheBuster, calculateSeed().toString());
params.set(urlParamNames.UrlPath, this.config.path);
params.set(urlParamNames.Referrer, this.config.pagesReferrer);
params.set(urlParamNames.Referrer, this.config.referrer);

if (this._conversionTrackingEnabled && this._cookieID) {
params.set(COOKIE_PARAM, this._cookieID);
}

if (this._visitor) {
params.set(urlParamNames.VisitorId, this._visitor.id);
if (this._visitor.method) params.set(urlParamNames.VisitorMethod, this._visitor.method);
}

return params;
}
Expand All @@ -104,13 +122,23 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{
return this.track(PageViewEvent);
}

/**
* returns the endpoint to hit depending on whether conversion tracking is enabled
* @private
*/
private endpoint(): string {
if (this._conversionTrackingEnabled) {
return `https://${DEFAULT_CONVERSION_TRACKING_DOMAIN}/${ENDPOINT}`;
}
return `https://${DEFAULT_DOMAIN_PAGES}/${ENDPOINT}`;
}

/** {@inheritDoc PagesAnalyticsService.setDebugEnabled} */
async track(event: PagesAnalyticsEvent): Promise<void> {
async track(event: PagesAnalyticsEvent, conversionInfo?: ConversionDetails): Promise<void> {
/** TODO: need to evaluate that the event name is valid, I think there are restrictions in the characters
* that are accepted
*/
const urlStr = `${DEFAULT_DOMAIN_PAGES}/store_pagespixel`;
const url = new URL(urlStr);
const url = new URL(this.endpoint());
url.search = this.urlParameters(event).toString();
const res = await this.httpRequesterService.get(url.toString());
// modern browsers won't let us access the status because of CORS
Expand All @@ -120,16 +148,33 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{
throw new Error(errorMessage);
}
this.printEvent(event);

if (this._conversionTrackingEnabled && this._cookieID && conversionInfo) {
this._conversionTracker.trackConversion({
cid: conversionInfo.cid,
cv: conversionInfo.cv,
cookieId: this._cookieID,
});
}
}

/** {@inheritDoc PagesAnalyticsService.setDebugEnabled} */
setDebugEnabled(enabled: boolean): void {
this._debug = enabled;
if (this._conversionTracker) {
this._conversionTracker.setDebugEnabled(enabled);
}
}

/** {@inheritDoc PagesAnalyticsService.setVisitor} */
setVisitor(visitor: Visitor | undefined): void {
this._visitor = visitor;
}

/** {@inheritDoc PagesAnalyticsService.setConversionTrackingEnabled} */
setConversionTrackingEnabled(enabled: boolean, cookieId: string): void {
this._conversionTrackingEnabled = enabled;
this._cookieID = cookieId;
}
}

16 changes: 8 additions & 8 deletions src/models/config/PagesAnalyticsConfig.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {BaseAnalyticsConfig} from './BaseAnalyticsConfig';
import {DirectoryPage} from '../pages';
import {EntityPage} from '../pages';
import {LocatorPage} from '../pages';
import {StaticPage} from '../pages';
import { BaseAnalyticsConfig } from './BaseAnalyticsConfig';
import { DirectoryPage } from '../pages';
import { EntityPage } from '../pages';
import { LocatorPage } from '../pages';
import { StaticPage } from '../pages';

/**
* The main configuration options for Pages Analytics. Contains all page or session level information.
Expand All @@ -29,9 +29,9 @@ export interface PagesAnalyticsConfig extends BaseAnalyticsConfig {
path: string

/**
* The url the user came from
* */
pagesReferrer: string,
* Page which sent the user to the current page, comes from typically Document.referrer
*/
referrer: string

/**
* The details of the page type
Expand Down
7 changes: 7 additions & 0 deletions src/models/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* URL Parameter & Param name for Yext conversion pixels
*
* @public
*/
export const COOKIE_PARAM = '_yfpc';
export const DEFAULT_CONVERSION_TRACKING_DOMAIN = 'realtimeanalytics.yext.com';
14 changes: 14 additions & 0 deletions src/models/conversiontracking/CommonConversionData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Shared properties of both ConversionEvent and ListingsClickEvent
*/
export interface CommonConversionData {
/**
* A cookie id from a first party cookie (i.e. from a visit to a domain you control)
*/
cookieId: string

/**
* Page which sent the user to the current page, comes from typically Document.referrer
*/
referrer?: string
}
16 changes: 16 additions & 0 deletions src/models/conversiontracking/ConversionDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* The details of an individual conversion event, without the cookie id.
*/
export interface ConversionDetails {
/**
* The id of the conversion tag, you can find the value from the conversion tracking section in your
* Yext account. You can find a list of tags under:
* https://www.yext.com/s/[your business id]/reports/conversiontracking/setup
*/
cid: string

/**
* Conversion Value Optional custom value supplied for this conversion
*/
cv?: string
}
31 changes: 4 additions & 27 deletions src/models/conversiontracking/ConversionEvent.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
import { CommonConversionData } from './CommonConversionData';
import { ConversionDetails } from './ConversionDetails';

/**
* An event representing a Conversion
*
* @public
*/
export interface ConversionEvent {
/**
* The id of the conversion tag, you can find the value from the conversion tracking section in your
* Yext account. You can find a list of tags under:
* https://www.yext.com/s/[your business id]/reports/conversiontracking/setup
*/
cid: string

/**
* Conversion Value Optional custom value supplied for this conversion
*/
cv?: string

/**
* A cookie id from a first party cookie (i.e. from a visit to a domain you control)
*/
firstPartyCookieId?: string

/**
* Page went sent the user to the current page
*/
referrer?: string

/**
* A cookie id from a third party, e.g. a Yext listings publisher
*/
thirdPartyCookieId?: string
export interface ConversionEvent extends CommonConversionData, ConversionDetails {
}
19 changes: 3 additions & 16 deletions src/models/conversiontracking/ListingsClickEvent.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { CommonConversionData } from './CommonConversionData';

/**
* An event representing a user arriving at a landing page from a publisher site.
*
* @public
*/
export interface ListingsClickEvent {
export interface ListingsClickEvent extends CommonConversionData{
/**
* The source parameter signifying which listings publisher should get credit
* Comes from the y_source URL Parameter.
Expand All @@ -14,19 +16,4 @@ export interface ListingsClickEvent {
* The url of the landing page.
*/
location: string,

/**
* Page went sent the user to the current page
*/
referrer?: string,

/**
* A cookie id from a first party cookie (i.e. from a visit to a domain you control)
*/
firstPartyCookieId?: string,

/**
* A cookie id from a third party, e.g. a Yext listings publisher
*/
thirdPartyCookieId?: string,
}
6 changes: 0 additions & 6 deletions src/models/conversiontracking/constants.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/models/conversiontracking/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { ListingsClickEvent } from './ListingsClickEvent';
export { ConversionEvent } from './ConversionEvent';
export { CookieParam } from './constants';
export { ConversionEvent } from './ConversionEvent';
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './search';
export * from './pages';
export * from './config';
export * from './conversiontracking';
export { COOKIE_PARAM } from './constants';
export { AnalyticsPayload } from './AnalyticsPayload';
2 changes: 1 addition & 1 deletion src/models/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './events';
export * from './PageTypes';
export * from './PageTypes';
Loading

0 comments on commit 383ab4b

Please sign in to comment.