Skip to content

Commit b9d6190

Browse files
danielbento92Duarte Amorim
authored andcommitted
feat: analytics referrer attribution spa on main
- Set up analytics behavior for referrer attribution in SPA; - Fix additional issues related to analytics context in tracking events; - Fix inconsistencies in analytics tests.
1 parent 5bb8b29 commit b9d6190

13 files changed

Lines changed: 281 additions & 134 deletions

File tree

packages/analytics/src/Analytics.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ class Analytics {
236236
*
237237
* @returns Promise that will resolve when the method finishes.
238238
*/
239-
protected async onLoadedIntegrations(
239+
protected onLoadedIntegrations(
240240
// eslint-disable-next-line @typescript-eslint/no-unused-vars
241241
loadedIntegrations: IntegrationRuntimeData[],
242-
): Promise<void> {
242+
): void | Promise<void> {
243243
// Do nothing
244244
}
245245

@@ -442,31 +442,29 @@ class Analytics {
442442
eventContext?: EventContextData,
443443
): Promise<this> {
444444
return await this.trackInternal(
445-
TrackType.Track,
446-
event,
447-
properties,
448-
eventContext,
445+
await this.getTrackEventData(
446+
TrackType.Track,
447+
event,
448+
properties,
449+
eventContext,
450+
),
449451
);
450452
}
451453

452454
/**
453455
* Internal track method used by the public track method. Builds the track object
454456
* with page default properties on the context.
455457
*
456-
* @param type - Type of event to be tracked.
457-
* @param event - Name of the event.
458-
* @param properties - Properties of the event.
459-
* @param eventContext - Context data that is specific for this event.
458+
* @param trackData - Data for the event.
460459
*
461460
* @returns Promise that will resolve with the instance that was used when calling this method to allow
462461
* chaining.
463462
*/
464463
protected async trackInternal(
465-
type: TrackTypesValues = TrackType.Track,
466-
event: string,
467-
properties?: EventProperties,
468-
eventContext?: EventContextData,
464+
trackData: EventData<TrackTypesValues>,
469465
): Promise<this> {
466+
const { event } = trackData;
467+
470468
if (!this.isReady) {
471469
logger.error(
472470
`Analytics tried to track the event ${event} but failed. Did you forget to call "analytics.ready()?"`,
@@ -486,15 +484,8 @@ class Analytics {
486484
await this.setUserPromise;
487485

488486
try {
489-
const data = await this.getTrackEventData(
490-
type,
491-
event,
492-
properties,
493-
eventContext,
494-
);
495-
496487
this.forEachIntegrationSafe(this.activeIntegrations, integration =>
497-
integration.track(data),
488+
integration.track(trackData),
498489
);
499490
} catch (error) {
500491
logger.error(

packages/analytics/src/__tests__/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Object {
211211
"LAST_FROM_PARAMETER_KEY": "__lastFromParameter",
212212
"LOAD_INTEGRATION_TRACK_TYPE": "loadIntegration",
213213
"ON_SET_USER_TRACK_TYPE": "onSetUser",
214+
"PAGE_LOCATION_REFERRER_KEY": "pageLocationReferrer",
214215
"StorageWrapper": [Function],
215216
"getCheckoutOrderIdentificationProperties": [Function],
216217
"getCheckoutProperties": [Function],

packages/analytics/src/__tests__/analytics.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,10 @@ describe('analytics', () => {
319319
const spyTrack = jest.spyOn(integrationInstance, 'track');
320320

321321
// @ts-expect-error Forcing call to trackInternal
322-
analytics.trackInternal(TrackType.Page, PageType.Homepage);
322+
analytics.trackInternal({
323+
type: TrackType.Page,
324+
event: PageType.Homepage,
325+
});
323326

324327
analytics.track('myEvent');
325328

@@ -951,7 +954,10 @@ describe('analytics', () => {
951954
);
952955

953956
// @ts-expect-error
954-
await analytics.trackInternal(TrackType.Page, PageType.Homepage);
957+
await analytics.trackInternal({
958+
type: TrackType.Page,
959+
event: PageType.Homepage,
960+
});
955961

956962
expect(spyTrack).toHaveBeenCalled();
957963

packages/analytics/src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export const ANALYTICS_UNIQUE_EVENT_ID = '__blackoutAnalyticsEventId';
2525
export const ANALYTICS_UNIQUE_VIEW_ID = '__uniqueViewId';
2626
export const ANALYTICS_PREVIOUS_UNIQUE_VIEW_ID = '__previousUniqueViewId';
2727
export const LAST_FROM_PARAMETER_KEY = '__lastFromParameter';
28+
export const PAGE_LOCATION_REFERRER_KEY = 'pageLocationReferrer';

packages/analytics/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export {
88
ANALYTICS_UNIQUE_VIEW_ID,
99
ANALYTICS_PREVIOUS_UNIQUE_VIEW_ID,
1010
LAST_FROM_PARAMETER_KEY,
11+
PAGE_LOCATION_REFERRER_KEY,
1112
} from './constants.js';
1213
export * from './defaults.js';
1314
export * from './getters.js';

packages/react/src/__tests__/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ Object {
251251
"integrations": Map {},
252252
"isReady": false,
253253
"lastFromParameter": null,
254+
"lastPageLocation": "",
254255
"platform": "web",
255256
"previousUniqueViewId": null,
256257
"setStoragePromise": Promise {},

packages/react/src/analytics/__tests__/analytics.test.ts

Lines changed: 133 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import AnalyticsCore, {
55
FromParameterType,
66
type IntegrationOptions,
77
integrations,
8+
PageType,
89
} from '@farfetch/blackout-analytics';
910
import TestStorage from 'test-storage';
1011
import type { WebContext } from '../context.js';
@@ -123,10 +124,14 @@ describe('analytics web', () => {
123124
await analytics.page(event, properties, eventContext);
124125

125126
expect(coreTrackSpy).toHaveBeenCalledWith(
126-
analyticsTrackTypes.Page,
127-
event,
128-
properties,
129-
eventContext,
127+
expect.objectContaining({
128+
type: analyticsTrackTypes.Page,
129+
event,
130+
properties,
131+
context: expect.objectContaining({
132+
event: expect.objectContaining(eventContext),
133+
}),
134+
}),
130135
);
131136

132137
// Allow the integration to run - this will trigger the flow to track the previously stored page() event
@@ -276,10 +281,130 @@ describe('analytics web', () => {
276281
await analytics.page(mockEvent, mockEventProperties, mockEventContext);
277282

278283
expect(coreTrackSpy).toHaveBeenCalledWith(
279-
analyticsTrackTypes.Page,
280-
mockEvent,
281-
mockEventProperties,
282-
mockEventContext,
284+
expect.objectContaining({
285+
type: analyticsTrackTypes.Page,
286+
event: mockEvent,
287+
properties: mockEventProperties,
288+
context: expect.objectContaining({
289+
event: expect.objectContaining(mockEventContext),
290+
}),
291+
}),
283292
);
284293
});
294+
295+
describe('Page Location Referrer Property', () => {
296+
// The 'page location referrer' property is significant within the analytics context, specifically concerning web pages.
297+
// It is essential to ensure, through various tests, that different scenarios verify the proper operation of references,
298+
// especially in the case of a Single Page Application (SPA).
299+
300+
const newAnalyticsIntance = () =>
301+
new (analytics.constructor as {
302+
new (): typeof analytics;
303+
})();
304+
// @ts-expect-error
305+
const coreTrackSpy = jest.spyOn(AnalyticsCore.prototype, 'trackInternal');
306+
307+
beforeEach(() => {
308+
coreTrackSpy.mockClear();
309+
});
310+
311+
it('Should retrieve pageLocationReferrer value from origin on first page view', async () => {
312+
const origin = 'www.example.com';
313+
314+
jest.spyOn(document, 'referrer', 'get').mockReturnValueOnce(origin);
315+
316+
const analyticsClean = newAnalyticsIntance();
317+
318+
await analyticsClean.page(PageType.Homepage);
319+
320+
expect(coreTrackSpy).toHaveBeenCalledWith(
321+
expect.objectContaining({
322+
type: analyticsTrackTypes.Page,
323+
event: PageType.Homepage,
324+
context: expect.objectContaining({
325+
web: expect.objectContaining({
326+
pageLocationReferrer: origin,
327+
}),
328+
}),
329+
}),
330+
);
331+
});
332+
333+
it('Should set `pageLocationReferrer` value to the previous page instead of document.referrer after the first navigation event', async () => {
334+
const origin = 'www.example.com';
335+
336+
jest.spyOn(document, 'referrer', 'get').mockReturnValueOnce(origin);
337+
window.location.href = `${origin}/${PageType.Homepage}`;
338+
339+
const analyticsClean = newAnalyticsIntance();
340+
341+
await analyticsClean.page(PageType.Homepage);
342+
343+
expect(coreTrackSpy).toHaveBeenCalledWith(
344+
expect.objectContaining({
345+
type: analyticsTrackTypes.Page,
346+
event: PageType.Homepage,
347+
context: expect.objectContaining({
348+
web: expect.objectContaining({
349+
pageLocationReferrer: origin,
350+
}),
351+
}),
352+
}),
353+
);
354+
355+
// set another page location
356+
window.location.href = `${origin}/${PageType.About}`;
357+
358+
await analyticsClean.page(PageType.About);
359+
360+
expect(coreTrackSpy).toHaveBeenCalledWith(
361+
expect.objectContaining({
362+
type: analyticsTrackTypes.Page,
363+
event: PageType.About,
364+
context: expect.objectContaining({
365+
web: expect.objectContaining({
366+
pageLocationReferrer: `${origin}/${PageType.Homepage}`,
367+
}),
368+
}),
369+
}),
370+
);
371+
});
372+
373+
it('should set `pageLocationReferrer` value to the previous page instead of document.referrer on track actions', async () => {
374+
const origin = 'www.example.com';
375+
376+
jest.spyOn(document, 'referrer', 'get').mockReturnValueOnce(origin);
377+
window.location.href = `${origin}/${PageType.Homepage}`;
378+
379+
const analyticsClean = newAnalyticsIntance();
380+
381+
await analyticsClean.track(mockEvent, mockEventProperties);
382+
383+
expect(coreTrackSpy).toHaveBeenCalledWith(
384+
expect.objectContaining({
385+
type: analyticsTrackTypes.Track,
386+
event: mockEvent,
387+
context: expect.objectContaining({
388+
web: expect.objectContaining({
389+
pageLocationReferrer: origin,
390+
}),
391+
}),
392+
}),
393+
);
394+
395+
await analyticsClean.page(PageType.Homepage);
396+
397+
expect(coreTrackSpy).toHaveBeenCalledWith(
398+
expect.objectContaining({
399+
type: analyticsTrackTypes.Page,
400+
event: PageType.Homepage,
401+
context: expect.objectContaining({
402+
web: expect.objectContaining({
403+
pageLocationReferrer: `${origin}/${PageType.Homepage}`,
404+
}),
405+
}),
406+
}),
407+
);
408+
});
409+
});
285410
});

packages/react/src/analytics/__tests__/context.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ describe('context', () => {
1515
title: document.title,
1616
referrer: document.referrer,
1717
},
18-
pageLocationReferrer: window.location.href,
1918
},
2019
};
2120

0 commit comments

Comments
 (0)