From 8b3957aa7595605700dbd5c23a806a376eb9b9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Thu, 2 May 2024 15:21:32 +0200 Subject: [PATCH 1/9] Add an option to generate the page view ID according to changes in the page URL to account for events tracked before page views in SPAs (close #1307) --- libraries/browser-tracker-core/src/state.ts | 2 + .../browser-tracker-core/src/tracker/index.ts | 46 ++- .../browser-tracker-core/src/tracker/types.ts | 25 ++ .../test/tracker/page_view_id.test.ts | 308 ++++++++++++++++++ 4 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 libraries/browser-tracker-core/test/tracker/page_view_id.test.ts diff --git a/libraries/browser-tracker-core/src/state.ts b/libraries/browser-tracker-core/src/state.ts index 75164e25d..d53a5ceba 100644 --- a/libraries/browser-tracker-core/src/state.ts +++ b/libraries/browser-tracker-core/src/state.ts @@ -52,6 +52,8 @@ export class SharedState { /* pageViewId, which can changed by other trackers on page; * initialized by tracker sent first event */ pageViewId?: string; + /* URL of the page view which the `pageViewId` was generated for */ + pageViewUrl?: string; } export function createSharedState(): SharedState { diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index 37e504fa2..a15a0f20c 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -48,6 +48,7 @@ import { ClientSession, ExtendedCrossDomainLinkerOptions, ParsedIdCookie, + PreservePageViewIdForUrl, } from './types'; import { parseIdCookie, @@ -292,6 +293,8 @@ export function Tracker( ), // Whether pageViewId should be regenerated after each trackPageView. Affect web_page context preservePageViewId = false, + // Whether pageViewId should be kept the same until the page URL changes. Affects web_page context + preservePageViewIdForUrl = trackerConfiguration.preservePageViewIdForUrl ?? false, // Whether first trackPageView was fired and pageViewId should not be changed anymore until reload pageViewSent = false, // Activity tracking config for callback and page ping variants @@ -719,6 +722,7 @@ export function Tracker( function resetPageView() { if (!preservePageViewId || state.pageViewId == null) { state.pageViewId = uuid(); + state.pageViewUrl = configCustomUrl || locationHrefAlias; } } @@ -727,10 +731,42 @@ export function Tracker( * Generates it if it wasn't initialized by other tracker */ function getPageViewId() { - if (state.pageViewId == null) { + if (shouldGenerateNewPageViewId()) { state.pageViewId = uuid(); + state.pageViewUrl = configCustomUrl || locationHrefAlias; + } + return state.pageViewId!; + } + + function shouldGenerateNewPageViewId() { + // If pageViewId is not initialized, generate it + if (state.pageViewId == null) { + return true; + } + // If pageViewId should be preserved regardless of the URL, don't generate a new one + if (preservePageViewId || !preservePageViewIdForUrl) { + return false; } - return state.pageViewId; + // If doesn't have previous URL in state, generate a new pageViewId + if (state.pageViewUrl === undefined) { + return true; + } + const current = configCustomUrl || locationHrefAlias; + // If full preserve is enabled, compare the full URL + if (preservePageViewIdForUrl === true || preservePageViewIdForUrl == 'full' || !('URL' in window)) { + return state.pageViewUrl != current; + } + const currentUrl = new URL(current); + const previousUrl = new URL(state.pageViewUrl); + // If pathname preserve is enabled, compare the pathname + if (preservePageViewIdForUrl == 'pathname') { + return currentUrl.pathname != previousUrl.pathname; + } + // If pathname and search preserve is enabled, compare the pathname and search + if (preservePageViewIdForUrl == 'pathnameAndSearch') { + return currentUrl.pathname != previousUrl.pathname || currentUrl.search != previousUrl.search; + } + return false; } /** @@ -943,7 +979,7 @@ export function Tracker( function logPageView({ title, context, timestamp, contextCallback }: PageViewEvent & CommonEventProperties) { refreshUrl(); - if (pageViewSent) { + if (pageViewSent && !preservePageViewIdForUrl) { // Do not reset pageViewId if previous events were not page_view resetPageView(); } @@ -1291,6 +1327,10 @@ export function Tracker( preservePageViewId = true; }, + preservePageViewIdForUrl: function (preserve: PreservePageViewIdForUrl) { + preservePageViewIdForUrl = preserve; + }, + disableAnonymousTracking: function (configuration?: DisableAnonymousTrackingConfiguration) { trackerConfiguration.anonymousTracking = false; diff --git a/libraries/browser-tracker-core/src/tracker/types.ts b/libraries/browser-tracker-core/src/tracker/types.ts index d6ce73c0f..a93973bd6 100755 --- a/libraries/browser-tracker-core/src/tracker/types.ts +++ b/libraries/browser-tracker-core/src/tracker/types.ts @@ -46,6 +46,9 @@ export type ExtendedCrossDomainLinkerAttributes = { export type ExtendedCrossDomainLinkerOptions = boolean | ExtendedCrossDomainLinkerAttributes; +/* Setting for the `preservePageViewIdForUrl` configuration that decides how to preserve the pageViewId on URL changes. */ +export type PreservePageViewIdForUrl = boolean | 'full' | 'pathname' | 'pathnameAndSearch'; + /** * The configuration object for initialising the tracker * @example @@ -266,6 +269,17 @@ export type TrackerConfiguration = { * @param data - The data associated with the event(s) that failed to send */ onRequestFailure?: (data: RequestFailure) => void; + + /** + * Decide how the `pageViewId` should be preserved based on the URL. + * If set to `false`, the `pageViewId` will be regenerated on the second and each following page view event (first page view doesn't change the page view ID since tracker initialization). + * If set to `true` or `'full'`, the `pageViewId` will be kept the same for all page views with that exact URL (even for events tracked before the page view event). + * If set to `'pathname'`, the `pageViewId` will be kept the same for all page views with the same pathname (search params or fragment may change). + * If set to `'pathnameAndSearch'`, the `pageViewId` will be kept the same for all page views with the same pathname and search params (fragment may change). + * If `preservePageViewId` is enabled, the `preservePageViewIdForUrl` setting is ignored. + * Defaults to `false`. + */ + preservePageViewIdForUrl?: PreservePageViewIdForUrl; }; /** @@ -603,6 +617,17 @@ export interface BrowserTracker { */ preservePageViewId: () => void; + /** + * Decide how the `pageViewId` should be preserved based on the URL. + * If set to `false`, the `pageViewId` will be regenerated on the second and each following page view event (first page view doesn't change the page view ID since tracker initialization). + * If set to `true` or `'full'`, the `pageViewId` will be kept the same for all page views with that exact URL (even for events tracked before the page view event). + * If set to `'pathname'`, the `pageViewId` will be kept the same for all page views with the same pathname (search params or fragment may change). + * If set to `'pathnameAndSearch'`, the `pageViewId` will be kept the same for all page views with the same pathname and search params (fragment may change). + * If `preservePageViewId` is enabled, the `preservePageViewIdForUrl` setting is ignored. + * Defaults to `false`. + */ + preservePageViewIdForUrl: (preserve: PreservePageViewIdForUrl) => void; + /** * Log visit to this page * diff --git a/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts new file mode 100644 index 000000000..87a26ad0e --- /dev/null +++ b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import { createTracker } from '../helpers'; + +describe('Tracker API: page view IDs', () => { + it('Keeps the page view ID when URL changes', () => { + const tracker = createTracker(); + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + tracker?.setCustomUrl('http://example.com/2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + describe('preservePageViewIdForUrl: false', () => { + it('Generates new page view ID on second page view', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl(false); + + const pageView1 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView2 = tracker?.getPageViewId(); + const pageView3 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView4 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + expect(pageView2).toEqual(pageView3); + expect(pageView3).not.toEqual(pageView4); + }); + + it("Doesn't generate new page view ID when URL is changed", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl(false); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + }); + + describe('preservePageViewIdForUrl: full', () => { + it("Doesn't generate new page view ID on second page view", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('full'); + + const pageView1 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView2 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView3 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + expect(pageView2).toEqual(pageView3); + }); + + it("Doesn't generate new page view ID for the same URL", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('full'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it('Generates new page view ID when URL changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('full'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + + it('Generates new page view ID when hash param changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('full'); + + tracker?.setCustomUrl('http://example.com/#1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/#2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + + it('Generates new page view ID when search param changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('full'); + + tracker?.setCustomUrl('http://example.com/?test=1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/?test=2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + + it('Works the same way if set to true', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl(true); + + tracker?.setCustomUrl('http://example.com/#1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/#2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + + it('Works the same way if configured through createTracker', () => { + const tracker = createTracker({ preservePageViewIdForUrl: 'full' }); + + tracker?.setCustomUrl('http://example.com/?test=1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/?test=2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + }); + + describe('preservePageViewIdForUrl: pathname', () => { + it("Doesn't generate new page view ID on second page view", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathname'); + + const pageView1 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView2 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView3 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + expect(pageView2).toEqual(pageView3); + }); + + it("Doesn't generate new page view ID for the same URL", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathname'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it("Doesn't generate new page view ID when hash param changes", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathname'); + + tracker?.setCustomUrl('http://example.com/#1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/#2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it("Doesn't generate new page view ID when search param changes", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathname'); + + tracker?.setCustomUrl('http://example.com/?test=1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/?test=2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it('Generates a new page view ID when the path changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathname'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + }); + + describe('preservePageViewIdForUrl: pathnameAndSearch', () => { + it("Doesn't generate new page view ID on second page view", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathnameAndSearch'); + + const pageView1 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView2 = tracker?.getPageViewId(); + + tracker?.trackPageView(); + const pageView3 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + expect(pageView2).toEqual(pageView3); + }); + + it("Doesn't generate new page view ID for the same URL", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathnameAndSearch'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it("Doesn't generate new page view ID when hash param changes", () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathnameAndSearch'); + + tracker?.setCustomUrl('http://example.com/#1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/#2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + }); + + it('Generates new page view ID when search param changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathnameAndSearch'); + + tracker?.setCustomUrl('http://example.com/?test=1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/?test=2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + + it('Generates a new page view ID when the path changes', () => { + const tracker = createTracker(); + tracker?.preservePageViewIdForUrl('pathnameAndSearch'); + + tracker?.setCustomUrl('http://example.com/1'); + const pageView1 = tracker?.getPageViewId(); + + tracker?.setCustomUrl('http://example.com/2'); + const pageView2 = tracker?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + }); + }); +}); From 5126cd9854ec7c84cff8f84db60a7415aefac3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Thu, 2 May 2024 15:52:11 +0200 Subject: [PATCH 2/9] Run rush change --- .../issue-1307-page_view_id_2024-05-02-13-51.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@snowplow/browser-tracker-core/issue-1307-page_view_id_2024-05-02-13-51.json diff --git a/common/changes/@snowplow/browser-tracker-core/issue-1307-page_view_id_2024-05-02-13-51.json b/common/changes/@snowplow/browser-tracker-core/issue-1307-page_view_id_2024-05-02-13-51.json new file mode 100644 index 000000000..56d2a4010 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker-core/issue-1307-page_view_id_2024-05-02-13-51.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker-core", + "comment": "Add an option to generate the page view ID according to changes in the page URL to account for events tracked before page views in SPAs (#1307 and #1125)", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker-core" +} \ No newline at end of file From 585f79f75ead1eaacd820e03269cbc48f56248c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Thu, 2 May 2024 16:12:50 +0200 Subject: [PATCH 3/9] Update docs and export new type --- api-docs/docs/browser-tracker/browser-tracker.api.md | 5 +++++ trackers/browser-tracker/src/index.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/api-docs/docs/browser-tracker/browser-tracker.api.md b/api-docs/docs/browser-tracker/browser-tracker.api.md index 12d8b1a63..5660e9f38 100644 --- a/api-docs/docs/browser-tracker/browser-tracker.api.md +++ b/api-docs/docs/browser-tracker/browser-tracker.api.md @@ -83,6 +83,7 @@ export interface BrowserTracker { namespace: string; newSession: () => void; preservePageViewId: () => void; + preservePageViewIdForUrl: (preserve: PreservePageViewIdForUrl) => void; setBufferSize: (newBufferSize: number) => void; setCollectorUrl: (collectorUrl: string) => void; setCookiePath: (path: string) => void; @@ -291,6 +292,9 @@ export type PostBatch = Record[]; // @public export function preservePageViewId(trackers?: Array): void; +// @public (undocumented) +export type PreservePageViewIdForUrl = boolean | "full" | "pathname" | "pathnameAndSearch"; + // @public export function removeGlobalContexts(contexts: Array, trackers?: Array): void; @@ -417,6 +421,7 @@ export type TrackerConfiguration = { retryFailedRequests?: boolean; onRequestSuccess?: (data: EventBatch) => void; onRequestFailure?: (data: RequestFailure) => void; + preservePageViewIdForUrl?: PreservePageViewIdForUrl; }; // @public diff --git a/trackers/browser-tracker/src/index.ts b/trackers/browser-tracker/src/index.ts index 874cb58d5..dfe3701b9 100644 --- a/trackers/browser-tracker/src/index.ts +++ b/trackers/browser-tracker/src/index.ts @@ -43,6 +43,7 @@ import { GetBatch, PostBatch, ParsedIdCookie, + PreservePageViewIdForUrl, } from '@snowplow/browser-tracker-core'; import { version } from '@snowplow/tracker-core'; @@ -91,6 +92,7 @@ export { GetBatch, PostBatch, ParsedIdCookie, + PreservePageViewIdForUrl, }; export { version }; export * from './api'; From 0d01bfe05f9a24d9092f34f7a43c6d31acfcbb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Thu, 2 May 2024 16:18:52 +0200 Subject: [PATCH 4/9] Run rush change again --- .../issue-1307-page_view_id_2024-05-02-14-18.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@snowplow/browser-tracker/issue-1307-page_view_id_2024-05-02-14-18.json diff --git a/common/changes/@snowplow/browser-tracker/issue-1307-page_view_id_2024-05-02-14-18.json b/common/changes/@snowplow/browser-tracker/issue-1307-page_view_id_2024-05-02-14-18.json new file mode 100644 index 000000000..a1acbb772 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker/issue-1307-page_view_id_2024-05-02-14-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker", + "comment": "Add an option to generate the page view ID according to changes in the page URL to account for events tracked before page views in SPAs (#1307 and #1125)", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker" +} \ No newline at end of file From 2fe42fde986dd0701f76c7cda7c02195a1469eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Thu, 2 May 2024 16:26:15 +0200 Subject: [PATCH 5/9] Increase max size for browser-tracker and browser-tracker-core --- .bundlemonrc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bundlemonrc.json b/.bundlemonrc.json index 466289b96..57965c60b 100644 --- a/.bundlemonrc.json +++ b/.bundlemonrc.json @@ -7,7 +7,7 @@ }, { "path": "./trackers/browser-tracker/dist/index.umd.min.js", - "maxSize": "15.5kb", + "maxSize": "16kb", "maxPercentIncrease": 10 }, { @@ -22,7 +22,7 @@ }, { "path": "./libraries/browser-tracker-core/dist/index.module.js", - "maxSize": "27kb", + "maxSize": "28kb", "maxPercentIncrease": 10 }, { From 1a332236267a07eb6d2febee2d88c4b671393def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Fri, 3 May 2024 12:26:24 +0200 Subject: [PATCH 6/9] Generate a new page view ID on the second page view tracked on the same page regardless of whether preservePageViewIdForUrl is enabled --- libraries/browser-tracker-core/src/tracker/index.ts | 3 ++- .../test/tracker/page_view_id.test.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index a15a0f20c..48a2c87c2 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -734,6 +734,7 @@ export function Tracker( if (shouldGenerateNewPageViewId()) { state.pageViewId = uuid(); state.pageViewUrl = configCustomUrl || locationHrefAlias; + pageViewSent = false; } return state.pageViewId!; } @@ -979,7 +980,7 @@ export function Tracker( function logPageView({ title, context, timestamp, contextCallback }: PageViewEvent & CommonEventProperties) { refreshUrl(); - if (pageViewSent && !preservePageViewIdForUrl) { + if (pageViewSent) { // Do not reset pageViewId if previous events were not page_view resetPageView(); } diff --git a/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts index 87a26ad0e..be06dd417 100644 --- a/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts +++ b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts @@ -75,7 +75,7 @@ describe('Tracker API: page view IDs', () => { }); describe('preservePageViewIdForUrl: full', () => { - it("Doesn't generate new page view ID on second page view", () => { + it("Generates new page view ID on second page view on the same URL", () => { const tracker = createTracker(); tracker?.preservePageViewIdForUrl('full'); @@ -88,7 +88,7 @@ describe('Tracker API: page view IDs', () => { const pageView3 = tracker?.getPageViewId(); expect(pageView1).toEqual(pageView2); - expect(pageView2).toEqual(pageView3); + expect(pageView2).not.toEqual(pageView3); }); it("Doesn't generate new page view ID for the same URL", () => { @@ -169,7 +169,7 @@ describe('Tracker API: page view IDs', () => { }); describe('preservePageViewIdForUrl: pathname', () => { - it("Doesn't generate new page view ID on second page view", () => { + it("Generates new page view ID on second page view on the same URL", () => { const tracker = createTracker(); tracker?.preservePageViewIdForUrl('pathname'); @@ -182,7 +182,7 @@ describe('Tracker API: page view IDs', () => { const pageView3 = tracker?.getPageViewId(); expect(pageView1).toEqual(pageView2); - expect(pageView2).toEqual(pageView3); + expect(pageView2).not.toEqual(pageView3); }); it("Doesn't generate new page view ID for the same URL", () => { @@ -238,7 +238,7 @@ describe('Tracker API: page view IDs', () => { }); describe('preservePageViewIdForUrl: pathnameAndSearch', () => { - it("Doesn't generate new page view ID on second page view", () => { + it("Generates new page view ID on second page view on the same URL", () => { const tracker = createTracker(); tracker?.preservePageViewIdForUrl('pathnameAndSearch'); @@ -251,7 +251,7 @@ describe('Tracker API: page view IDs', () => { const pageView3 = tracker?.getPageViewId(); expect(pageView1).toEqual(pageView2); - expect(pageView2).toEqual(pageView3); + expect(pageView2).not.toEqual(pageView3); }); it("Doesn't generate new page view ID for the same URL", () => { From f0620b993fdac2372fba12f48e17de474f343960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Fri, 3 May 2024 17:23:52 +0200 Subject: [PATCH 7/9] Handle multiple trackers with shared state such that they share the page view IDs for events tracked after each other --- .../browser-tracker-core/src/tracker/index.ts | 11 ++-- .../test/helpers/index.ts | 4 +- .../test/tracker/page_view_id.test.ts | 51 +++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index 48a2c87c2..c5e7d01de 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -295,8 +295,8 @@ export function Tracker( preservePageViewId = false, // Whether pageViewId should be kept the same until the page URL changes. Affects web_page context preservePageViewIdForUrl = trackerConfiguration.preservePageViewIdForUrl ?? false, - // Whether first trackPageView was fired and pageViewId should not be changed anymore until reload - pageViewSent = false, + // The pageViewId of the last page view event or undefined if no page view tracked yet. Used to determine if pageViewId should be regenerated for a new page view. + lastSentPageViewId: string | undefined = undefined, // Activity tracking config for callback and page ping variants activityTrackingConfig: ActivityTrackingConfig = { enabled: false, @@ -734,7 +734,6 @@ export function Tracker( if (shouldGenerateNewPageViewId()) { state.pageViewId = uuid(); state.pageViewUrl = configCustomUrl || locationHrefAlias; - pageViewSent = false; } return state.pageViewId!; } @@ -980,11 +979,11 @@ export function Tracker( function logPageView({ title, context, timestamp, contextCallback }: PageViewEvent & CommonEventProperties) { refreshUrl(); - if (pageViewSent) { - // Do not reset pageViewId if previous events were not page_view + if (lastSentPageViewId && lastSentPageViewId == getPageViewId()) { + // Do not reset pageViewId if a page view was not tracked yet or a different page view ID was used (in order to support multiple trackers with shared state) resetPageView(); } - pageViewSent = true; + lastSentPageViewId = getPageViewId(); // So we know what document.title was at the time of trackPageView lastDocumentTitle = document.title; diff --git a/libraries/browser-tracker-core/test/helpers/index.ts b/libraries/browser-tracker-core/test/helpers/index.ts index b2a617fc3..b2a3acff3 100644 --- a/libraries/browser-tracker-core/test/helpers/index.ts +++ b/libraries/browser-tracker-core/test/helpers/index.ts @@ -75,7 +75,7 @@ export function createTestSessionIdCookie(params?: CreateTestSessionIdCookie) { return `_sp_ses.${domainHash}=*; Expires=; Path=/; SameSite=None; Secure;`; } -export function createTracker(configuration?: TrackerConfiguration) { +export function createTracker(configuration?: TrackerConfiguration, sharedState?: SharedState) { let id = 'sp-' + Math.random(); - return addTracker(id, id, '', '', new SharedState(), configuration); + return addTracker(id, id, '', '', sharedState ?? new SharedState(), configuration); } diff --git a/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts index be06dd417..e573a5fd2 100644 --- a/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts +++ b/libraries/browser-tracker-core/test/tracker/page_view_id.test.ts @@ -305,4 +305,55 @@ describe('Tracker API: page view IDs', () => { expect(pageView1).not.toEqual(pageView2); }); }); + + describe('Multiple trackers with a shared state', () => { + it('Keeps the page view ID for each page view pairs', () => { + const tracker1 = createTracker(); + const tracker2 = createTracker(undefined, tracker1?.sharedState); + + tracker1?.setCustomUrl('http://example.com/1'); + tracker1?.trackPageView(); + const pageView1 = tracker1?.getPageViewId(); + + tracker2?.trackPageView(); + const pageView2 = tracker2?.getPageViewId(); + + expect(pageView1).toEqual(pageView2); + + tracker1?.trackPageView(); + const pageView3 = tracker1?.getPageViewId(); + + tracker2?.trackPageView(); + const pageView4 = tracker2?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView3); + expect(pageView3).toEqual(pageView4); + }); + + it("Doesn't keep the page view ID for multiple calls to one tracker", () => { + const tracker1 = createTracker(); + const tracker2 = createTracker(undefined, tracker1?.sharedState); + + tracker1?.setCustomUrl('http://example.com/1'); + tracker1?.trackPageView(); + const pageView1 = tracker1?.getPageViewId(); + tracker1?.trackPageView(); + const pageView2 = tracker1?.getPageViewId(); + + tracker2?.trackPageView(); + const pageView3 = tracker2?.getPageViewId(); + + expect(pageView1).not.toEqual(pageView2); + expect(pageView2).toEqual(pageView3); + + tracker1?.trackPageView(); + const pageView4 = tracker1?.getPageViewId(); + + tracker2?.trackPageView(); + const pageView5 = tracker2?.getPageViewId(); + + expect(pageView3).not.toEqual(pageView4); + expect(pageView4).toEqual(pageView5); + }); + }); }); From 093f6b3facf03f2f4231d44f56b9286d193a0c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Wed, 15 May 2024 08:19:53 +0200 Subject: [PATCH 8/9] Pin Micro version to 2.0.0 to fix integration tests --- trackers/javascript-tracker/package.json | 2 +- trackers/javascript-tracker/test/micro.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/trackers/javascript-tracker/package.json b/trackers/javascript-tracker/package.json index 0325482f3..d83cbf35e 100644 --- a/trackers/javascript-tracker/package.json +++ b/trackers/javascript-tracker/package.json @@ -30,7 +30,7 @@ ], "scripts": { "build": "rollup -c --silent --failAfterWarnings", - "docker:micro": "docker pull snowplow/snowplow-micro:latest", + "docker:micro": "docker pull snowplow/snowplow-micro:2.0.0", "test": "jest test/unit/*.test.ts --no-cache", "test:build": "rollup --config rollup.config.test.js --silent", "test:e2e:local": "npm-run-all --parallel test:build docker:micro --serial wdio:local", diff --git a/trackers/javascript-tracker/test/micro.ts b/trackers/javascript-tracker/test/micro.ts index 92ddf80d1..1081f2241 100644 --- a/trackers/javascript-tracker/test/micro.ts +++ b/trackers/javascript-tracker/test/micro.ts @@ -17,7 +17,7 @@ const docker = new Docker(); export const start = (isRemote?: boolean) => { return docker .createContainer({ - Image: 'snowplow/snowplow-micro:latest', + Image: 'snowplow/snowplow-micro:2.0.0', AttachStdin: false, AttachStdout: true, AttachStderr: true, From 38786f0863642413b4870d59ff1cdb5a2b83f1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matu=CC=81s=CC=8C=20Tomlein?= Date: Wed, 15 May 2024 08:21:37 +0200 Subject: [PATCH 9/9] Run rush change --- .../issue-1307-page_view_id_2024-05-15-06-21.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@snowplow/javascript-tracker/issue-1307-page_view_id_2024-05-15-06-21.json diff --git a/common/changes/@snowplow/javascript-tracker/issue-1307-page_view_id_2024-05-15-06-21.json b/common/changes/@snowplow/javascript-tracker/issue-1307-page_view_id_2024-05-15-06-21.json new file mode 100644 index 000000000..985490b5a --- /dev/null +++ b/common/changes/@snowplow/javascript-tracker/issue-1307-page_view_id_2024-05-15-06-21.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/javascript-tracker", + "comment": "Fix running integration tests due to a problem with Micro", + "type": "none" + } + ], + "packageName": "@snowplow/javascript-tracker" +} \ No newline at end of file