diff --git a/docs/analytics.commonconversiondata.cookieid.md b/docs/analytics.commonconversiondata.cookieid.md new file mode 100644 index 00000000..e7c676f1 --- /dev/null +++ b/docs/analytics.commonconversiondata.cookieid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [CommonConversionData](./analytics.commonconversiondata.md) > [cookieId](./analytics.commonconversiondata.cookieid.md) + +## CommonConversionData.cookieId property + +A cookie id from a first party cookie (i.e. from a visit to a domain you control) + +Signature: + +```typescript +cookieId: string; +``` diff --git a/docs/analytics.commonconversiondata.md b/docs/analytics.commonconversiondata.md new file mode 100644 index 00000000..c2f1299e --- /dev/null +++ b/docs/analytics.commonconversiondata.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [CommonConversionData](./analytics.commonconversiondata.md) + +## CommonConversionData interface + +Shared properties of both ConversionEvent and ListingsClickEvent + +Signature: + +```typescript +export interface CommonConversionData +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [cookieId](./analytics.commonconversiondata.cookieid.md) | string | A cookie id from a first party cookie (i.e. from a visit to a domain you control) | +| [referrer?](./analytics.commonconversiondata.referrer.md) | string | (Optional) Page which sent the user to the current page, comes from typically Document.referrer | + diff --git a/docs/analytics.commonconversiondata.referrer.md b/docs/analytics.commonconversiondata.referrer.md new file mode 100644 index 00000000..be39e398 --- /dev/null +++ b/docs/analytics.commonconversiondata.referrer.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [CommonConversionData](./analytics.commonconversiondata.md) > [referrer](./analytics.commonconversiondata.referrer.md) + +## CommonConversionData.referrer property + +Page which sent the user to the current page, comes from typically Document.referrer + +Signature: + +```typescript +referrer?: string; +``` diff --git a/docs/analytics.conversiondetails.cid.md b/docs/analytics.conversiondetails.cid.md new file mode 100644 index 00000000..cd230937 --- /dev/null +++ b/docs/analytics.conversiondetails.cid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [ConversionDetails](./analytics.conversiondetails.md) > [cid](./analytics.conversiondetails.cid.md) + +## ConversionDetails.cid property + +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 + +Signature: + +```typescript +cid: string; +``` diff --git a/docs/analytics.conversiondetails.cv.md b/docs/analytics.conversiondetails.cv.md new file mode 100644 index 00000000..e93de409 --- /dev/null +++ b/docs/analytics.conversiondetails.cv.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [ConversionDetails](./analytics.conversiondetails.md) > [cv](./analytics.conversiondetails.cv.md) + +## ConversionDetails.cv property + +Conversion Value Optional custom value supplied for this conversion + +Signature: + +```typescript +cv?: string; +``` diff --git a/docs/analytics.conversiondetails.md b/docs/analytics.conversiondetails.md new file mode 100644 index 00000000..2e72d041 --- /dev/null +++ b/docs/analytics.conversiondetails.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [@yext/analytics](./analytics.md) > [ConversionDetails](./analytics.conversiondetails.md) + +## ConversionDetails interface + +The details of an individual conversion event, without the cookie id. + +Signature: + +```typescript +export interface ConversionDetails +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [cid](./analytics.conversiondetails.cid.md) | string | 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 | +| [cv?](./analytics.conversiondetails.cv.md) | string | (Optional) Conversion Value Optional custom value supplied for this conversion | + diff --git a/docs/analytics.conversionevent.md b/docs/analytics.conversionevent.md index 2fdd37d9..0b5976e8 100644 --- a/docs/analytics.conversionevent.md +++ b/docs/analytics.conversionevent.md @@ -11,5 +11,5 @@ An event representing a Conversion ```typescript export interface ConversionEvent extends CommonConversionData, ConversionDetails ``` -Extends: CommonConversionData, ConversionDetails +Extends: [CommonConversionData](./analytics.commonconversiondata.md), [ConversionDetails](./analytics.conversiondetails.md) diff --git a/docs/analytics.listingsclickevent.md b/docs/analytics.listingsclickevent.md index 9c7747ba..db36dec9 100644 --- a/docs/analytics.listingsclickevent.md +++ b/docs/analytics.listingsclickevent.md @@ -11,7 +11,7 @@ An event representing a user arriving at a landing page from a publisher site. ```typescript export interface ListingsClickEvent extends CommonConversionData ``` -Extends: CommonConversionData +Extends: [CommonConversionData](./analytics.commonconversiondata.md) ## Properties diff --git a/docs/analytics.md b/docs/analytics.md index 90ea0e5d..5df45137 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -29,6 +29,8 @@ | [AnalyticsPayload](./analytics.analyticspayload.md) | The shape of the data which is sent during an analytics request. | | [AutocompleteEvent](./analytics.autocompleteevent.md) | Event for autocomplete selection. | | [BaseAnalyticsConfig](./analytics.baseanalyticsconfig.md) | Base analytics configuration | +| [CommonConversionData](./analytics.commonconversiondata.md) | Shared properties of both ConversionEvent and ListingsClickEvent | +| [ConversionDetails](./analytics.conversiondetails.md) | The details of an individual conversion event, without the cookie id. | | [ConversionEvent](./analytics.conversionevent.md) | An event representing a Conversion | | [ConversionTrackingService](./analytics.conversiontrackingservice.md) | A service for tracking conversions | | [CtaEvent](./analytics.ctaevent.md) | A call to action analytics event. | diff --git a/docs/analytics.pagesanalyticsconfig.md b/docs/analytics.pagesanalyticsconfig.md index 7121d8fa..b1a97da6 100644 --- a/docs/analytics.pagesanalyticsconfig.md +++ b/docs/analytics.pagesanalyticsconfig.md @@ -18,7 +18,7 @@ export interface PagesAnalyticsConfig extends BaseAnalyticsConfig | Property | Type | Description | | --- | --- | --- | | [pageType](./analytics.pagesanalyticsconfig.pagetype.md) | [DirectoryPage](./analytics.directorypage.md) \| [EntityPage](./analytics.entitypage.md) \| [LocatorPage](./analytics.locatorpage.md) \| [StaticPage](./analytics.staticpage.md) | The details of the page type | -| [path](./analytics.pagesanalyticsconfig.path.md) | string | The path component of the page url | +| [pageUrl](./analytics.pagesanalyticsconfig.pageurl.md) | string | The full url of the page we are on, typically window.location.href | | [production](./analytics.pagesanalyticsconfig.production.md) | boolean | Set to true if the environment is production If set to true events will appear in Analytics Reports in your Yext Account | | [referrer](./analytics.pagesanalyticsconfig.referrer.md) | string | Page which sent the user to the current page, comes from typically Document.referrer | | [siteId](./analytics.pagesanalyticsconfig.siteid.md) | number | The ID of the Pages Site Can be easily found from the url of the Deploy Page in your Yext Account e.g. https://www.yext.com/s/\[businessId\]/yextsites/\[siteid\]/branch/\[branchId\]/deploys/recent | diff --git a/docs/analytics.pagesanalyticsconfig.path.md b/docs/analytics.pagesanalyticsconfig.pageurl.md similarity index 56% rename from docs/analytics.pagesanalyticsconfig.path.md rename to docs/analytics.pagesanalyticsconfig.pageurl.md index 21e03f58..3cab101f 100644 --- a/docs/analytics.pagesanalyticsconfig.path.md +++ b/docs/analytics.pagesanalyticsconfig.pageurl.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [@yext/analytics](./analytics.md) > [PagesAnalyticsConfig](./analytics.pagesanalyticsconfig.md) > [path](./analytics.pagesanalyticsconfig.path.md) +[Home](./index.md) > [@yext/analytics](./analytics.md) > [PagesAnalyticsConfig](./analytics.pagesanalyticsconfig.md) > [pageUrl](./analytics.pagesanalyticsconfig.pageurl.md) -## PagesAnalyticsConfig.path property +## PagesAnalyticsConfig.pageUrl property -The path component of the page url +The full url of the page we are on, typically window.location.href Signature: ```typescript -path: string; +pageUrl: string; ``` diff --git a/docs/analytics.pagesanalyticsservice.track.md b/docs/analytics.pagesanalyticsservice.track.md index 3178b0f6..2ba01cc7 100644 --- a/docs/analytics.pagesanalyticsservice.track.md +++ b/docs/analytics.pagesanalyticsservice.track.md @@ -17,7 +17,7 @@ track(event: PagesAnalyticsEvent, conversionInfo?: ConversionDetails): PromiseReturns: diff --git a/etc/analytics.api.md b/etc/analytics.api.md index 1ec48228..7b26239c 100644 --- a/etc/analytics.api.md +++ b/etc/analytics.api.md @@ -38,9 +38,18 @@ export interface BaseAnalyticsConfig { visitor?: Visitor; } -// Warning: (ae-forgotten-export) The symbol "CommonConversionData" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "ConversionDetails" needs to be exported by the entry point index.d.ts -// +// @public +export interface CommonConversionData { + cookieId: string; + referrer?: string; +} + +// @public +export interface ConversionDetails { + cid: string; + cv?: string; +} + // @public export interface ConversionEvent extends CommonConversionData, ConversionDetails { } @@ -120,7 +129,7 @@ export interface LocatorPage extends PageType { // @public export interface PagesAnalyticsConfig extends BaseAnalyticsConfig { pageType: DirectoryPage | EntityPage | LocatorPage | StaticPage; - path: string; + pageUrl: string; production: boolean; referrer: string; siteId: number; diff --git a/src/infra/ConversionTrackingReporter.ts b/src/infra/ConversionTrackingReporter.ts index fef2e4e0..d6b284a9 100644 --- a/src/infra/ConversionTrackingReporter.ts +++ b/src/infra/ConversionTrackingReporter.ts @@ -1,5 +1,5 @@ import { ConversionEvent, COOKIE_PARAM, ListingsClickEvent } from '../models'; -import { DEFAULT_CONVERSION_TRACKING_DOMAIN } from '../models/constants'; +import { DEFAULT_CONVERSION_TRACKING_DOMAIN, LISTINGS_SOURCE_PARAM } from '../models/constants'; import { CommonConversionData } from '../models/conversiontracking/CommonConversionData'; import { ConversionTrackingService, HttpRequesterService } from '../services'; import { calculateSeed } from './CalculateSeed'; @@ -60,7 +60,7 @@ export class ConversionTrackingReporter implements ConversionTrackingService { async trackListings(event: ListingsClickEvent): Promise { const url = new URL(`https://${DEFAULT_CONVERSION_TRACKING_DOMAIN}/${listingsEndpoint}`); const params = new URLSearchParams(); - params.set('y_source', event.source); + params.set(LISTINGS_SOURCE_PARAM, event.source); params.set('location', event.location); ConversionTrackingReporter.formatBaseEvent(event, params); url.search = params.toString(); diff --git a/src/infra/PagesAnalyticsReporter.ts b/src/infra/PagesAnalyticsReporter.ts index 405b4dca..18e82e41 100644 --- a/src/infra/PagesAnalyticsReporter.ts +++ b/src/infra/PagesAnalyticsReporter.ts @@ -1,4 +1,4 @@ -import { COOKIE_PARAM, DEFAULT_CONVERSION_TRACKING_DOMAIN } from '../models/constants'; +import { COOKIE_PARAM, DEFAULT_CONVERSION_TRACKING_DOMAIN, LISTINGS_SOURCE_PARAM } from '../models/constants'; import { ConversionDetails } from '../models/conversiontracking/ConversionDetails'; import { HttpRequesterService, PagesAnalyticsService } from '../services'; import { DefaultPagesEventNames, PagesAnalyticsConfig, Visitor } from '../models'; @@ -55,12 +55,20 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{ private _debug: boolean|undefined; private _conversionTrackingEnabled: boolean|undefined; private _cookieID: string|undefined; - private _conversionTracker: ConversionTrackingReporter; + private readonly _conversionTracker: ConversionTrackingReporter; + private _hasTrackedListings: boolean; + private readonly _pageUrl: URL; constructor(private config: PagesAnalyticsConfig, private httpRequesterService: HttpRequesterService) { this.setVisitor(config.visitor); this._debug = config.debug; this._conversionTracker = new ConversionTrackingReporter(this.httpRequesterService, this._debug); + this._hasTrackedListings = false; + try { + this._pageUrl = new URL(config.pageUrl); + } catch { + throw new Error(`pageUrl property must be a valid URL, was: '${config.pageUrl}'`); + } } /** @@ -102,7 +110,7 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{ } params.set(urlParamNames.CacheBuster, calculateSeed().toString()); - params.set(urlParamNames.UrlPath, this.config.path); + params.set(urlParamNames.UrlPath, this._pageUrl.pathname); params.set(urlParamNames.Referrer, this.config.referrer); if (this._conversionTrackingEnabled && this._cookieID) { @@ -119,6 +127,19 @@ export class PagesAnalyticsReporter implements PagesAnalyticsService{ /** {@inheritDoc PagesAnalyticsService.pageView} */ async pageView(): Promise { + const sourceValue = this._pageUrl.searchParams.get(LISTINGS_SOURCE_PARAM); + + if (this._conversionTrackingEnabled + && this._cookieID + && !this._hasTrackedListings + && sourceValue) { + await this._conversionTracker.trackListings({ + cookieId: this._cookieID, + location: this._pageUrl.toString(), + source: sourceValue, + }); + this._hasTrackedListings = true; + } return this.track(PageViewEvent); } diff --git a/src/models/config/PagesAnalyticsConfig.ts b/src/models/config/PagesAnalyticsConfig.ts index 0eb5c2c1..59c37068 100644 --- a/src/models/config/PagesAnalyticsConfig.ts +++ b/src/models/config/PagesAnalyticsConfig.ts @@ -24,9 +24,9 @@ export interface PagesAnalyticsConfig extends BaseAnalyticsConfig { production: boolean, /** - * The path component of the page url + * The full url of the page we are on, typically window.location.href */ - path: string + pageUrl: string /** * Page which sent the user to the current page, comes from typically Document.referrer diff --git a/src/models/constants.ts b/src/models/constants.ts index 4adabbf9..a3873511 100644 --- a/src/models/constants.ts +++ b/src/models/constants.ts @@ -4,4 +4,5 @@ * @public */ export const COOKIE_PARAM = '_yfpc'; +export const LISTINGS_SOURCE_PARAM = 'y_source'; export const DEFAULT_CONVERSION_TRACKING_DOMAIN = 'realtimeanalytics.yext.com'; \ No newline at end of file diff --git a/src/models/conversiontracking/CommonConversionData.ts b/src/models/conversiontracking/CommonConversionData.ts index 82668353..df3849fc 100644 --- a/src/models/conversiontracking/CommonConversionData.ts +++ b/src/models/conversiontracking/CommonConversionData.ts @@ -1,5 +1,7 @@ /** * Shared properties of both ConversionEvent and ListingsClickEvent + * + * @public */ export interface CommonConversionData { /** diff --git a/src/models/conversiontracking/ConversionDetails.ts b/src/models/conversiontracking/ConversionDetails.ts index c44605e3..60973aef 100644 --- a/src/models/conversiontracking/ConversionDetails.ts +++ b/src/models/conversiontracking/ConversionDetails.ts @@ -1,5 +1,7 @@ /** * The details of an individual conversion event, without the cookie id. + * + * @public */ export interface ConversionDetails { /** diff --git a/src/services/__mocks__/MockHttpRequesterService.ts b/src/services/__mocks__/MockHttpRequesterService.ts index 841e3957..a3369052 100644 --- a/src/services/__mocks__/MockHttpRequesterService.ts +++ b/src/services/__mocks__/MockHttpRequesterService.ts @@ -1,10 +1,12 @@ import 'isomorphic-fetch'; import { HttpRequesterService } from '../HttpRequesterService'; -export const mockHttpRequesterService: HttpRequesterService = { - post: jest.fn(() => Promise.resolve(new Response())), - get: jest.fn(() => Promise.resolve(new Response())), -}; +export function mockHttpRequesterService(): HttpRequesterService { + return { + post: jest.fn(() => Promise.resolve(new Response())), + get: jest.fn(() => Promise.resolve(new Response())), + }; +} export function mockErrorHttpRequesterService(message: string): HttpRequesterService { return { diff --git a/test-site/src/index.ts b/test-site/src/index.ts index e3711efc..9812b887 100644 --- a/test-site/src/index.ts +++ b/test-site/src/index.ts @@ -36,10 +36,10 @@ const pages = providePagesAnalytics({ pageType: { name: 'entity', pageSetId: 'location', - id: '18718615', + id: 18718615, }, referrer: 'https://www.google.com', - path: '/location/11291', + pageUrl: 'https://www.pagesanalyticstesting.com/location/11291?y_source=123353131212312312', businessId: 3350634, production: false, siteId: 40659, diff --git a/tests/infra/ConversionTrackingReporter.ts b/tests/infra/ConversionTrackingReporter.ts index 29f14386..0aa315a7 100644 --- a/tests/infra/ConversionTrackingReporter.ts +++ b/tests/infra/ConversionTrackingReporter.ts @@ -8,25 +8,27 @@ beforeEach(() => { jest.spyOn(global.Date, 'now').mockReturnValue(1); }); -const reporter = new ConversionTrackingReporter(mockHttpRequesterService); - it('should not set empty parameters', () => { + const mockService = mockHttpRequesterService(); + const reporter = new ConversionTrackingReporter(mockService); reporter.trackConversion({ cid: '12345', cookieId: '54321', }); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith( + expect(mockService.get).toHaveBeenLastCalledWith( 'https://realtimeanalytics.yext.com/conversiontracking/conversion?cid=12345&_yfpc=54321&v=1001', ); }); it('should set all parameters passed', () => { + const mockService = mockHttpRequesterService(); + const reporter = new ConversionTrackingReporter(mockService); reporter.trackConversion({ cid: '12345', cookieId: '54321', referrer: 'http://www.google.com/foo/bar', }); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith( + expect(mockService.get).toHaveBeenLastCalledWith( 'https://realtimeanalytics.yext.com/conversiontracking/conversion?cid=12345&_yfpc=54321&referrer=http%3A%2F%2Fwww.google.com%2Ffoo%2Fbar&v=1001', ); }); @@ -42,24 +44,28 @@ it('should handle an error', () => { }); it('should track listings', () => { + const mockService = mockHttpRequesterService(); + const reporter = new ConversionTrackingReporter(mockService); reporter.trackListings({ cookieId: '54321', source: 'foo', location: 'https://www.example.com/my/foo/page' }); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith( + expect(mockService.get).toHaveBeenLastCalledWith( 'https://realtimeanalytics.yext.com/listings?y_source=foo&location=https%3A%2F%2Fwww.example.com%2Fmy%2Ffoo%2Fpage&_yfpc=54321&v=1001', ); }); it('should track listings with more details', () => { + const mockService = mockHttpRequesterService(); + const reporter = new ConversionTrackingReporter(mockService); reporter.trackListings({ source: 'foo', location: 'https://www.example.com/my/foo/page', cookieId: '54321', referrer: 'http://www.google.com/foo/bar', }); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith( + expect(mockService.get).toHaveBeenLastCalledWith( 'https://realtimeanalytics.yext.com/listings?y_source=foo&location=https%3A%2F%2Fwww.example.com%2Fmy%2Ffoo%2Fpage&_yfpc=54321&referrer=http%3A%2F%2Fwww.google.com%2Ffoo%2Fbar&v=1001', ); }); \ No newline at end of file diff --git a/tests/infra/PagesAnalyticsReporter.ts b/tests/infra/PagesAnalyticsReporter.ts index e7e8e48a..f76a7ef4 100644 --- a/tests/infra/PagesAnalyticsReporter.ts +++ b/tests/infra/PagesAnalyticsReporter.ts @@ -8,17 +8,18 @@ beforeEach(() => { }); it('The static page page view URL is constructed correctly', () => { + const httpRequesterService = mockHttpRequesterService(); const reporter = new PagesAnalyticsReporter({ pageType: { staticPageId: 'My Page Set', name: 'static', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.pageView(); const expectedUrl = new URL('https://www.yext-pixel.com/store_pagespixel'); @@ -34,7 +35,7 @@ it('The static page page view URL is constructed correctly', () => { expectedUrl.searchParams.set('pageurl', '/foo/bar'); expectedUrl.searchParams.set('pagesReferrer','https://www.google.com'); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('Should handle http errors properly', () => { @@ -47,7 +48,7 @@ it('Should handle http errors properly', () => { name: 'static', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 @@ -57,6 +58,7 @@ it('Should handle http errors properly', () => { }); it('should track entity pages', () => { + const httpRequesterService = mockHttpRequesterService(); const expectedUrl = new URL('https://www.yext-pixel.com/store_pagespixel'); expectedUrl.searchParams.set('businessids', '0'); expectedUrl.searchParams.set('product', 'sites'); @@ -77,17 +79,18 @@ it('should track entity pages', () => { id: 1, }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.pageView(); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('should track directory pages', () => { + const httpRequesterService = mockHttpRequesterService(); const reporter = new PagesAnalyticsReporter({ pageType: { name: 'directory', @@ -95,11 +98,11 @@ it('should track directory pages', () => { id: 1, }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.pageView(); const expectedUrl = new URL('https://www.yext-pixel.com/store_pagespixel'); @@ -116,21 +119,22 @@ it('should track directory pages', () => { expectedUrl.searchParams.set('pageurl', '/foo/bar'); expectedUrl.searchParams.set('pagesReferrer','https://www.google.com'); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('should track locator pages', () => { + const httpRequesterService = mockHttpRequesterService(); const reporter = new PagesAnalyticsReporter({ pageType: { name: 'locator', searchId: 'My Locator Page Set', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.pageView(); const expectedUrl = new URL('https://www.yext-pixel.com/store_pagespixel'); @@ -145,23 +149,23 @@ it('should track locator pages', () => { expectedUrl.searchParams.set('pageurl', '/foo/bar'); expectedUrl.searchParams.set('pagesReferrer','https://www.google.com'); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('should track custom events', () => { + const httpRequesterService = mockHttpRequesterService(); const eventName = 'my_event_type_name'; - const reporter = new PagesAnalyticsReporter({ pageType: { name: 'static', staticPageId: 'My Page Set', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.track({eventType: eventName}); const expectedUrl = new URL('https://www.yext-pixel.com/store_pagespixel'); @@ -177,21 +181,22 @@ it('should track custom events', () => { expectedUrl.searchParams.set('pageurl', '/foo/bar'); expectedUrl.searchParams.set('pagesReferrer','https://www.google.com'); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('should use set the visitor', () => { + const httpRequesterService = mockHttpRequesterService(); const reporter = new PagesAnalyticsReporter({ pageType: { name: 'static', staticPageId: 'My Page Set', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.setVisitor({ id: 'foo', @@ -215,21 +220,22 @@ it('should use set the visitor', () => { expectedUrl.searchParams.set('visitorId', 'foo'); expectedUrl.searchParams.set('visitorIdMethod', 'bar'); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); }); it('should use conversion tracking endpoint and set cookie', () => { + const httpRequesterService = mockHttpRequesterService(); const reporter = new PagesAnalyticsReporter({ pageType: { name: 'static', staticPageId: 'My Page Set', }, referrer: 'https://www.google.com', - path: '/foo/bar', + pageUrl: 'https://www.foobar.com/foo/bar', businessId: 0, production: false, siteId: 0 - }, mockHttpRequesterService); + }, httpRequesterService); reporter.setVisitor({ id: 'foo', @@ -255,6 +261,68 @@ it('should use conversion tracking endpoint and set cookie', () => { expectedUrl.searchParams.set('visitorId', 'foo'); expectedUrl.searchParams.set('visitorIdMethod', 'bar'); - console.log(expectedUrl.toString()); - expect(mockHttpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); + expect(httpRequesterService.get).toHaveBeenLastCalledWith(expectedUrl.toString()); +}); + +it('should throw an error if the pageUrl property is invalid', () => { + const httpRequesterService = mockHttpRequesterService(); + expect.assertions(1); + const errMsg = 'pageUrl property must be a valid URL, was: \'foo/bar\''; + const thisShouldThrow = () => { + new PagesAnalyticsReporter({ + pageType: { + staticPageId: 'My Page Set', + name: 'static', + }, + referrer: 'https://www.google.com', + pageUrl: 'foo/bar', + businessId: 0, + production: false, + siteId: 0 + }, httpRequesterService); + }; + + expect(thisShouldThrow).toThrow(errMsg); +}); + +it('should track listings with a pageview', async () => { + const httpRequesterService = mockHttpRequesterService(); + + const reporter = new PagesAnalyticsReporter({ + pageType: { + name: 'static', + staticPageId: 'My Page Set', + }, + referrer: 'https://www.google.com', + pageUrl: 'https://www.foobar.com/foo/bar?y_source=123455', + businessId: 0, + production: false, + siteId: 0 + }, httpRequesterService); + + reporter.setConversionTrackingEnabled(true, '123456'); + await reporter.pageView(); + + const listingsUrl = new URL('https://realtimeanalytics.yext.com/listings'); + listingsUrl.searchParams.set('y_source', '123455'); + listingsUrl.searchParams.set('location', 'https://www.foobar.com/foo/bar?y_source=123455'); + listingsUrl.searchParams.set('_yfpc', '123456'); + listingsUrl.searchParams.set('v', '1001'); + + expect(httpRequesterService.get).toHaveBeenNthCalledWith(1, listingsUrl.toString()); + + const pageViewUrl = new URL('https://realtimeanalytics.yext.com/store_pagespixel'); + pageViewUrl.searchParams.set('businessids', '0'); + pageViewUrl.searchParams.set('product', 'sites'); + pageViewUrl.searchParams.set('siteId', '0'); + pageViewUrl.searchParams.set('isStaging', 'true'); + pageViewUrl.searchParams.set('eventType', 'pageview'); + pageViewUrl.searchParams.set('pageType', 'static'); + pageViewUrl.searchParams.set('staticPageId', 'My Page Set'); + pageViewUrl.searchParams.set('v', '1001'); + pageViewUrl.searchParams.set('pageurl', '/foo/bar'); + pageViewUrl.searchParams.set('pagesReferrer','https://www.google.com'); + + pageViewUrl.searchParams.set('_yfpc', '123456'); + expect(httpRequesterService.get).toHaveBeenNthCalledWith(2, pageViewUrl.toString()); }); \ No newline at end of file diff --git a/tests/infra/SearchAnalyticsReporter.ts b/tests/infra/SearchAnalyticsReporter.ts index 42c43623..80b4a172 100644 --- a/tests/infra/SearchAnalyticsReporter.ts +++ b/tests/infra/SearchAnalyticsReporter.ts @@ -9,10 +9,11 @@ const config: SearchAnalyticsConfig = { }; it('The URL is constructed correctly', () => { - const analyticsReporter = new SearchAnalyticsReporter(config, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter(config, mockService); analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1' }); const expectedUrl = `https://answers.yext-pixel.com/realtimeanalytics/data/answers/${config.businessId}`; - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expectedUrl, expect.anything()); + expect(mockService.post).toHaveBeenLastCalledWith(expectedUrl, expect.anything()); }); it('The URL is constructed correctly with custom domains', () => { @@ -20,14 +21,16 @@ it('The URL is constructed correctly with custom domains', () => { ...config, domain: 'https://yext.com' }; - const analyticsReporter = new SearchAnalyticsReporter(configWithCustomDomain, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter(configWithCustomDomain, mockService); analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1'}); const expectedUrl = `https://yext.com/realtimeanalytics/data/answers/${config.businessId}`; - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expectedUrl, expect.anything()); + expect(mockService.post).toHaveBeenLastCalledWith(expectedUrl, expect.anything()); }); it('The data is structured properly', () => { - const analyticsReporter = new SearchAnalyticsReporter(config, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter(config, mockService); analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1'}); const expectedData = { data: { @@ -38,11 +41,12 @@ it('The data is structured properly', () => { queryId: '1' } }; - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expect.anything(), expectedData); + expect(mockService.post).toHaveBeenLastCalledWith(expect.anything(), expectedData); }); it('Additional params are sent properly', () => { - const analyticsReporter = new SearchAnalyticsReporter(config, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter(config, mockService); const additionalRequestAttributes = { ytag: 123 }; @@ -57,12 +61,13 @@ it('Additional params are sent properly', () => { }, ...additionalRequestAttributes }; - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expect.anything(), expectedData); + expect(mockService.post).toHaveBeenLastCalledWith(expect.anything(), expectedData); }); it('Returns a resolved promise after a successful report', () => { expect.assertions(1); - const analyticsReporter = new SearchAnalyticsReporter(config, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter(config, mockService); const resPromise = analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1' }); expect(resPromise).resolves.toEqual(undefined); }); @@ -77,7 +82,8 @@ it('Performs a promise rejection when the API responds with an error', () => { it('Visitor is set and passed properly', () => { const visitorParam = { visitor: { id: '123'} }; - const analyticsReporter = new SearchAnalyticsReporter({...config, ...visitorParam}, mockHttpRequesterService); + const mockService = mockHttpRequesterService(); + const analyticsReporter = new SearchAnalyticsReporter({...config, ...visitorParam}, mockService); analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1' }); const data = { businessId: 123, @@ -92,10 +98,10 @@ it('Visitor is set and passed properly', () => { ...visitorParam } }; - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expect.anything(), + expect(mockService.post).toHaveBeenLastCalledWith(expect.anything(), expectedDataWithVisitor); analyticsReporter.setVisitor(undefined); analyticsReporter.report({ type: 'SCROLL_TO_BOTTOM_OF_PAGE', queryId: '1' }); - expect(mockHttpRequesterService.post).toHaveBeenLastCalledWith(expect.anything(), { data }); + expect(mockService.post).toHaveBeenLastCalledWith(expect.anything(), { data }); });