Skip to content

Commit 486c7f7

Browse files
committed
Add the option to regenerate the page view ID with each page view event (close #436)
1 parent 2ac6987 commit 486c7f7

File tree

7 files changed

+74
-26
lines changed

7 files changed

+74
-26
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"sha1": "git://github.com/pvorb/node-sha1.git#v1.0.0"
1212
},
1313
"devDependencies": {
14+
"js-base64": "2.1.9",
1415
"JSON": "1.0.0",
1516
"grunt": "0.4.5",
1617
"grunt-browserify": "5.0.0",

src/js/in_queue.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* after the Tracker has been initialized and loaded
4747
************************************************************/
4848

49-
object.InQueueManager = function(TrackerConstructor, version, pageViewId, mutSnowplowState, asyncQueue, functionName) {
49+
object.InQueueManager = function(TrackerConstructor, version, mutSnowplowState, asyncQueue, functionName) {
5050

5151
// Page view ID should be shared between all tracker instances
5252
var trackerDictionary = {};
@@ -112,7 +112,7 @@
112112
argmap = argmap || {};
113113

114114
if (!trackerDictionary.hasOwnProperty(namespace)) {
115-
trackerDictionary[namespace] = new TrackerConstructor(functionName, namespace, version, pageViewId, mutSnowplowState, argmap);
115+
trackerDictionary[namespace] = new TrackerConstructor(functionName, namespace, version, mutSnowplowState, argmap);
116116
trackerDictionary[namespace].setCollectorUrl(endpoint);
117117
} else {
118118
helpers.warn('Tracker namespace ' + namespace + ' already exists.');

src/js/snowplow.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@
9292
/* Tracker identifier with version */
9393
version = 'js-' + '<%= pkg.version %>', // Update banner.js too
9494

95-
pageViewId = uuid.v4(),
96-
9795
/* Contains four variables that are shared with tracker.js and must be passed by reference */
9896
mutSnowplowState = {
9997

@@ -106,7 +104,11 @@
106104

107105
/* DOM Ready */
108106
hasLoaded: false,
109-
registeredOnLoadHandlers: []
107+
registeredOnLoadHandlers: [],
108+
109+
/* pageViewId, which can changed by other trackers on page;
110+
* initialized by tracker sent first event */
111+
pageViewId: null
110112
};
111113

112114
/************************************************************
@@ -127,7 +129,7 @@
127129
// Flush all POST queues
128130
lodash.forEach(mutSnowplowState.bufferFlushers, function (flusher) {
129131
flusher();
130-
})
132+
});
131133

132134
/*
133135
* Delay/pause (blocks UI)
@@ -223,7 +225,7 @@
223225
* @param string distSubdomain The subdomain on your CloudFront collector's distribution
224226
*/
225227
getTrackerCf: function (distSubdomain) {
226-
var t = new tracker.Tracker(functionName, '', version, pageViewId, mutSnowplowState, {});
228+
var t = new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
227229
t.setCollectorCf(distSubdomain);
228230
return t;
229231
},
@@ -235,7 +237,7 @@
235237
* @param string rawUrl The collector URL minus protocol and /i
236238
*/
237239
getTrackerUrl: function (rawUrl) {
238-
var t = new tracker.Tracker(functionName, '', version, pageViewId, mutSnowplowState, {});
240+
var t = new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
239241
t.setCollectorUrl(rawUrl);
240242
return t;
241243
},
@@ -246,7 +248,7 @@
246248
* @return Tracker
247249
*/
248250
getAsyncTracker: function () {
249-
return new tracker.Tracker(functionName, '', version, pageViewId, mutSnowplowState, {});
251+
return new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
250252
}
251253
};
252254

@@ -259,7 +261,7 @@
259261
addReadyListener();
260262

261263
// Now replace initialization array with queue manager object
262-
return new queue.InQueueManager(tracker.Tracker, version, pageViewId, mutSnowplowState, asynchronousQueue, functionName);
264+
return new queue.InQueueManager(tracker.Tracker, version, mutSnowplowState, asynchronousQueue, functionName);
263265
};
264266

265267
}());

src/js/tracker.js

+40-9
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
* @param functionName global function name
5757
* @param namespace The namespace of the tracker object
5858
* @param version The current version of the JavaScript Tracker
59-
* @param pageViewId ID for the current page view, to be attached to all events in the web_page context
6059
* @param mutSnowplowState An object containing hasLoaded, registeredOnLoadHandlers, and expireDateTime
6160
* Passed in by reference in case they are altered by snowplow.js
6261
* @param argmap Optional dictionary of configuration options. Supported fields and their default values:
@@ -82,7 +81,7 @@
8281
* 19. maxPostBytes, 40000
8382
* 20. discoverRootDomain, false
8483
*/
85-
object.Tracker = function Tracker(functionName, namespace, version, pageViewId, mutSnowplowState, argmap) {
84+
object.Tracker = function Tracker(functionName, namespace, version, mutSnowplowState, argmap) {
8685

8786
/************************************************************
8887
* Private members
@@ -264,16 +263,15 @@
264263
commonContexts = [],
265264

266265
// Enhanced Ecommerce Contexts to be added on every `trackEnhancedEcommerceAction` call
267-
enhancedEcommerceContexts = [];
266+
enhancedEcommerceContexts = [],
267+
268+
// Whether pageViewId should be regenerated after each trackPageView. Affect web_page context
269+
preservePageViewId = false;
268270

269271
if (argmap.hasOwnProperty('discoverRootDomain') && argmap.discoverRootDomain) {
270272
configCookieDomain = helpers.findRootDomain();
271273
}
272274

273-
if (autoContexts.webPage) {
274-
commonContexts.push(getWebPageContext());
275-
}
276-
277275
if (autoContexts.gaCookies) {
278276
commonContexts.push(getGaCookiesContext());
279277
}
@@ -748,6 +746,10 @@
748746
function addCommonContexts(userContexts) {
749747
var combinedContexts = commonContexts.concat(userContexts || []);
750748

749+
if (autoContexts.webPage) {
750+
combinedContexts.push(getWebPageContext());
751+
}
752+
751753
// Add PerformanceTiming Context
752754
if (autoContexts.performanceTiming) {
753755
var performanceTimingContext = getPerformanceTimingContext();
@@ -813,6 +815,27 @@
813815
return combinedContexts;
814816
}
815817

818+
/**
819+
* Initialize new `pageViewId` if it shouldn't be preserved.
820+
* Should be called when `trackPageView` is invoked
821+
*/
822+
function resetPageView() {
823+
if (!preservePageViewId || mutSnowplowState.pageViewId == null) {
824+
mutSnowplowState.pageViewId = uuid.v4();
825+
}
826+
}
827+
828+
/**
829+
* Safe function to get `pageViewId`.
830+
* Generates it if it wasn't initialized by other tracker
831+
*/
832+
function getPageViewId() {
833+
if (mutSnowplowState.pageViewId == null) {
834+
mutSnowplowState.pageViewId = uuid.v4();
835+
}
836+
return mutSnowplowState.pageViewId
837+
}
838+
816839
/**
817840
* Put together a web page context with a unique UUID for the page view
818841
*
@@ -822,7 +845,7 @@
822845
return {
823846
schema: 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0',
824847
data: {
825-
id: pageViewId
848+
id: getPageViewId()
826849
}
827850
};
828851
}
@@ -1147,6 +1170,7 @@
11471170
function logPageView(customTitle, context, contextCallback, tstamp) {
11481171

11491172
refreshUrl();
1173+
resetPageView();
11501174

11511175
// So we know what document.title was at the time of trackPageView
11521176
lastDocumentTitle = documentAlias.title;
@@ -1162,7 +1186,7 @@
11621186
purify(customReferrer || configReferrerUrl),
11631187
addCommonContexts(finalizeContexts(context, contextCallback)),
11641188
tstamp);
1165-
1189+
11661190
// Send ping (to log that user has stayed on page)
11671191
var now = new Date();
11681192
if (configMinimumVisitTime && configHeartBeatTimer && !activityTrackingInstalled) {
@@ -2202,6 +2226,13 @@
22022226
trackError: function (message, filename, lineno, colno, error, contexts) {
22032227
var enrichedContexts = addCommonContexts(contexts);
22042228
errorManager.trackError(message, filename, lineno, colno, error, enrichedContexts);
2229+
},
2230+
2231+
/**
2232+
* Stop regenerating `pageViewId` (available from `web_page` context)
2233+
*/
2234+
preservePageViewId: function () {
2235+
preservePageViewId = true
22052236
}
22062237
};
22072238
};

tests/integration/integration.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ define([
3737
'intern/chai!assert',
3838
'intern/dojo/node!lodash',
3939
'intern/dojo/node!http',
40-
'intern/dojo/node!url'
41-
], function(registerSuite, assert, lodash, http, url) {
40+
'intern/dojo/node!url',
41+
"intern/dojo/node!js-base64"
42+
], function(registerSuite, assert, lodash, http, url, jsBase64) {
43+
var decodeBase64 = jsBase64.Base64.fromBase64;
4244

4345
/**
4446
* Expected amount of request for each browser
@@ -195,9 +197,15 @@ define([
195197
// We cannot test more because implementations vary much in old browsers (FF27,IE9)
196198
return (event.schema === 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1') &&
197199
(event.data.programmingLanguage === 'JAVASCRIPT') &&
198-
(event.data.message != null)
200+
(event.data.message != null);
199201
}
200202
}));
201-
}
203+
},
204+
205+
206+
'Check pageViewId is regenerated for each trackPageView': function () {
207+
assert.isTrue(pageViewsHaveDifferentIds());
208+
}
209+
202210
});
203211
});

tests/nonfunctional/in_queue.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ define([
4040

4141
var output = 0;
4242

43-
function mockTrackerConstructor (functionName, namespace, version, sessionId, mutSnowplowState, argmap) {
43+
function mockTrackerConstructor (functionName, namespace, version, mutSnowplowState, argmap) {
4444
var configCollectorUrl,
4545
attribute = 10;
4646

@@ -68,7 +68,7 @@ define([
6868
["increaseAttribute", 5],
6969
["setOutputToAttribute"]
7070
];
71-
asyncQueue = new in_queue.InQueueManager(mockTrackerConstructor, 0, "c7a63a97-ac4a-4d58-8774-33b985701d16", {}, asyncQueue, 'snowplow');
71+
asyncQueue = new in_queue.InQueueManager(mockTrackerConstructor, 0, {}, asyncQueue, 'snowplow');
7272

7373
registerSuite({
7474
name: "InQueueManager test",

tests/pages/integration-template.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
window.snowplow('newTracker', 'cf', subdomain + '.ngrok.io', {
2525
encodeBase64: true,
2626
appId: 'CFe23a',
27-
platform: 'mob'
27+
platform: 'mob',
28+
contexts: {
29+
webPage: true
30+
}
2831
});
2932

3033
window.snowplow('setUserId', 'Malcolm');
@@ -37,6 +40,9 @@
3740
}
3841
]);
3942

43+
// This should have different pageViewId in web_page context
44+
window.snowplow('trackPageView');
45+
4046
window.snowplow('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
4147
window.snowplow('trackUnstructEvent', {
4248
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',

0 commit comments

Comments
 (0)