We have three custom dimensions defined in Google Analytics:
ClientId (dimension1): Session-scoped
SessionId (dimension2): Session-scoped
Hit Timestamp (dimension3): Hit-scoped
And these are being fed from an on-page script:
$(document).ready(function() {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
function getClientId() {
try {
var trackers = ga.getAll();
var i, len;
for (i= 0, len = trackers.length; i < len; i += 1) {
if (trackers[i].get('trackingId') === 'UA-XXXXXXXX-1') {
return trackers[i].get('clientId');
}
}
}
catch(e){
//do nothing
}
return 'false';
}
function getSessionId() {
return new Date().getTime() + '.' + Math.random().toString(36).substring(5);
}
function getTimeStamp() {
return new Date().getTime();
}
ga('create', 'UA-XXXXXXXX-1', 'auto');
ga('require', 'displayfeatures');
ga('require', 'linkid', 'linkid.js');
var clientId = getClientId();
var sessionId = getSessionId();
var timeStamp = getTimeStamp();
ga('send', 'pageview', {
'dimension1' : clientId,
'dimension2' : sessionId,
'dimension3' : timeStamp
});
});
</script>
Now, the marketing team tells us that ClientId is not getting captured. They shared data where we had some 24,000 rows, out of which only two had valid client ids. By contrast, Session ID and Hit Timestamp are being captured perfectly.
When I do a quick check on the website (by assigning the code for getClientId() to another temporary function and calling it), I get the ClientId.
I'm really not sure what's causing this to be missed on the live website. Can someone point out something that might be awry?
You should consider loading the GA library as recommended in the <head> section of your site rather than on $(document).ready. This is so that the analytics tracking has the soonest possibility to start tracking user engagements. In addition, if you load the analytics after DOM is ready or after the page has completely loaded, then there is the chance you miss some metrics if, for example, a user lands on your page, and then navigates away before the analytics library has a chance to capture their data.
Related
My code uses Javascript window.open to link to another domain. The url of the target page is dynamically generated. I want to track the analytics across the domains but cannot use the standard "linker" automatic method of gtag,js which automatically adds the _ga parameter to the 'a' tag href attribute or action attribute for a 'form'. I need to be able to manually add the_ga attribute to the dynamically generated URL which is then used in the window.open.
I have tried creating a dummy 'a href' and then simulating a 'click' using dispatchEvent with a preventDefault in the event handler. This works for a real click event (the _ga parameter is appended to the href URL which I can then extract and use) but not for a simulated click event with dispatchEvent.
The client ID can be easily obtained using the old analytics.js implementation but I can't find how to do it using gtag.js.
To put it simply, using analytics.js the following code can be used to obtain the "linkerParm" field:
ga(function(tracker) {
var linkerParam = tracker.get('linkerParam');
});
What is the gtag.js equivalent?
Can anyone help?
OK, I’ve figured out how to do this and thought I’d share it here. This is working for me but I can’t guarantee that it’s the best way.
It appears that gtag.js also loads analytics.js automatically, however it does not do the ‘create’ to set up the tracking ID.
In this example I am assuming that the standard global objects are used, window.dataLayer for gtag.js and window.ga for analytics.js.
function getLinkerParam(){
// our global variables
window.glpReady = false; // set to true when glpLinkerParam is set
window.glpLinkerParam = null; // the linkerParam
// define variables will be used in this function
var analyticsTimeout = null;
var timeoutFired = false;
var timeoutPeriod = 1000;
// Code to initialse analytics.js if necessary
function initialiseAnalytics(){
if (!window.GoogleAnalyticsObject){
window.GoogleAnalyticsObject = 'ga';
window.ga = window.ga || function(){
(window.ga.q = window.ga.q || []).push(arguments);
};
window.ga.l = 1 * new Date();
}
}
// initialise analytics.js if necessary
checker: {
// is gtag.js installed?
if (window.dataLayer) { // Yes
// obtain the tracking ID from the dataLayer
for (var i = 0; i < window.dataLayer.length; i++){
if (window.dataLayer[i][0] == 'config') {
// initialise the tracking with a create
console.log('trackingID='+window.dataLayer[i][1]);
initialiseAnalytics();
window.ga('create', window.dataLayer[i][1]);
break checker;
}
}
}
else {
// gtag.js is NOT installed
// check for analytics.js installed
if (window.GoogleAnalyticsObject){
// analytics.js is installed in it;s own right
// so the create should already be done.
break checker;
}
}
console.log('analytics not installed or config tracker id not found');
window.glpReady = true;
return;
} // end checker
// here analytics.js is defined either in it's own right
// or as part of gtag.js. In either case the tracking ID should have been 'created'.
ga('require', 'linker');
ga(function(tracker) {
console.log('Client ID: '+tracker.get('clientId'));
});
// set a timeout for the linkerParam
analyticsTimeout = setTimeout(function(){
timeoutFired = true;
console.log('get linkerParam timeout fired');
window.glpReady = true;
return;
}, timeoutPeriod);
// get the linkerParam
console.log('Get linkerParam');
ga(function(tracker) {
var linkerParam = tracker.get('linkerParam');
console.log('got linkerParam: '+linkerParam);
clearTimeout(analyticsTimeout);
window.glpLinkerParam = linkerParam;
window.glpReady = true;
return;
});
}
I hope this helps someone.
I'm currently upgrading my company's website across to analytics.js from ga.js
As I understand, _setDomainName, _addIgnoredRef & _trackEvent are deprecated and need updating.
Can anyone confirm that the below is correct please?
gaq.push(['_setDomainName', 'domain.net']);
This should be replaced with:
cookieDomain: 'domain.net',
legacyCookieDomain: 'domain.net'
_gaq.push( ['_addIgnoredRef', 'www.domain.com']);
This now has to be set in the Analytics admin section?
_gaq.push(['_trackEvent', 'Registration', 'New Account', '<organisation>']);
Should be replaced with:
ga('send', {
hitType: 'event',
eventCategory: ' Registration ',
eventAction: ''New Account ',
eventLabel: '<organisation>'
});
Any help would be gratefully received,
Thanks
How does this final example look?
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-xxxxx', 'domain.net');
ga('send', 'pageview');
ga('send', {
hitType: 'event',
eventCategory: ' Registration ',
eventAction: ''New Account ',
eventLabel: '<organisation>'
});
</script>
You are mostly right except for the cookie domain (at least it'S not obvious from your example that you got it right).
Cookie domain can either be passed as third parameter when you create your tracker:
ga('create', 'UA-XXXXX-Y', 'domain.com');
You can set the parameter to "auto" (IIRC that's the default) and the highest possible level in the domain will be selected as cookie domain (i.e. on subdomain.domain.com auto will set the cookie domain to domain.com).
You can also pass a configuration object with cookieDomain as a property:
ga('create', 'UA-XXXXX-Y', {
'cookieDomain': 'domain.com'
});
which is a useful syntax if you want to set multiple settings at once (you would pass each setting as key/value pair in the configuration object).
As you found out for yourself _addIgnoredRef is now replaced with the referral exclusion list, and your event tracking syntax is fine.
Does a "user scope" dimension will keep track on the changed to the dimension value?
Will it remember the value that was set to the dimension on a past period even if it was already being updated?
Will the dimension value it always correlated to the report time period or that it is being overwrite every update?
To back up DalmTo's comment with some evidence I did the science and built a very small test page:
<html>
<head>
<title>Test</title>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXX-XX', 'auto');
var d = new Date();
var currentTime = d.toLocaleTimeString();
var dimensionValue = 'I am userscope' + d + " - " + currentTime;
ga('set', 'dimension1', dimensionValue);
var dimensionValue = 'I am sessionscope' + d + " - " + currentTime;
ga('set', 'dimension2', dimensionValue);
var dimensionValue = 'I am hitscope' + d + " - " + currentTime;
ga('set', 'dimension3', dimensionValue);
ga('send', 'pageview');
</script>
</head>
<body>
</body>
</html>
So this sends a stupid message plus the datetime as dimension value.
The purpose of the test case was to make sure that nobody but me accesses the site and that all calls come from the same client id/user (albeit in multiple sessions). As an added benefit with just a few hits processing time in GA is just a few minutes, so I got to see the results quickly.
I called this a few times around 12 o'clock and then again a few minutes ago.
Results:
hit scoped custom dimensions are set per hit (no suprise here)
session scoped dimensions get the last value in the session (dito)
user scoped dimensions get the last value in the session. But they are assigned per session, not per user.
Potentially relevent screenshot:
These are two sessions for the same user which nonetheless show two different values for a user scoped custom dimension. So it's like Daimto said, only now you have a test case you can reproduce and do not need to rely on anyone's experience :-)
Of course that means you should take care yourself not to set user scoped dimensions more than once per user (else you might get inconsistencies in your segments).
(For completeness sake this would probably need to be repeated in a UserId view with session stitching enabled, but I doubt that it'll make much of a difference).
The documentation indicates that the userId must be set this way:
ga('create', 'UA-XXXX-Y', { 'userId': 'USER_ID' });
But in a Single Page Application (SPA), the user starts as anonymous and then logs in. So the app will start with:
ga('create', 'UA-XXXX-Y', 'auto');
And when he logs in, then I would like to change to a specific ID for tracking that user, but when I try:
ga('create', 'UA-XXXX-Y', { 'userId': 'USER_ID' });
Nothing happens, the user ID does not appears in subsequent requests.
Which is the right way of setting the userId at runtime?
Thanks.
Unfortunately, the documentation is currently incorrect. It is possible to set the user ID outside of the create method.
The reason your example isn't working is because you're calling create twice. What you want to do is call set. Here's how:
// Create the tracker instance.
ga('create', 'UA-XXXX-Y', 'auto');
// Once you know the user ID, set it on the current tracker.
ga('set', { userId: USER_ID });
Now all subsequent hits sent to GA will be associated with this user ID.
UPDATE:
The user ID documentation now reflects that it can be set outside of the create method.
I have a website which is using Google Analytics newer asynchronous tracking method (_gaq). The problem I've run into is that I want to institute some specific link tracking and am worried that I will be creating a race condition.
Basically, it's a news website so it has headlines which link to stories all over the place. A headline for a story might appear in 3 different places on a page, and appear on hundreds of other pages. Thus, in order to understand how our audience is interacting with the site we have to track how each specific headline block is used, and not just the destination. Because of those two stipulations tracking individual pages, nor tracking referred pages won't be enough, we have to track individual links.
So if I have a link.
Here
Because _gaq.push() is an asynchronous call, isn't it possible that the page change will occur prior to Google's completion of the click tracking? If so is there a way to prevent that, or do I have a misunderstanding about the way that Google Analytics Async functions (http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html).
You're right. If the browser leaves the page before it sends the GA tracking beacon (gif hit) for the event, the event will not be recorded. This is not new to the async code however, because the process of sending the tracking beacon is asynchronous; the old code worked the same way in that respect. If tracking is really that important, you could do something like this:
function track(link) {
if (!_gat) return true;
_gaq.push(['_trackEvent', 'stuff']);
setTimeout(function() {location.href=link.href'}, 200);
return false;
}
...
This will stop the browser from going to the next page when the link is clicked if GA has been loaded already (it's probably best to not make the user wait that long). Then it sends the event and waits 200 milliseconds to send the user to the href of the link they clicked on. This increases the likelihood that the event will be recorded. You can increase the likelihood even more by making the timeout longer, but that also may be hurting user-experience in the process. It's a balance you'll have to experiment with.
I've got this problem too, and am determined to find a real solution.
What about pushing the function into the queue?
// Log a pageview to GA for a conversion
_gaq.push(['_trackPageview', url]);
// Push the redirect to make sure it happens AFTER we track the pageview
_gaq.push(function() { document.location = url; });
From Google's documentation for universal analytics (new version since most other answers for this question). You can now easily specify a callback.
var trackOutboundLink = function(url) {
ga('send', 'event', 'outbound', 'click', url, {'hitCallback':
function () {
document.location = url;
}
});
}
For clarity I'd recommend using this syntax, which makes it clearer which properties you're sending and easier to add more :
ga('send', 'event', {
'eventCategory': 'Homepage',
'eventAction': 'Video Play',
'eventLabel': label,
'eventValue': null,
'hitCallback': function()
{
// redirect here
},
'transport': 'beacon',
'nonInteraction': (interactive || true ? 0 : 1)
});
[Here's a complete list of parameters for all possible ga calls.]
In addition I've added the transport parameter set to beacon (not actually needed because it's automatically set if appropriate):
This specifies the transport mechanism with which hits will be sent.
The options are 'beacon', 'xhr', or 'image'. By default, analytics.js
will try to figure out the best method based on the hit size and
browser capabilities. If you specify 'beacon' and the user's browser
does not support the navigator.sendBeacon method, it will fall back
to 'image' or 'xhr' depending on hit size.
So when using navigator.beacon the navigation won't interrupt the tracking . Unfortunately Microsoft's support for beacon is non existent so you should still put the redirect in a callback.
In event handler you should setup hit callback:
_gaq.push(['_set', 'hitCallback', function(){
document.location = ...
}]);
send you data
_gaq.push(['_trackEvent'
and stop event event processing
e.preventDefault();
e.stopPropagation();
I'm trying out a new approach where we build the URL for utm.gif ourselves, and request it, then only once we've received the response (the gif) we send the user on their way:
Usage:
trackPageview(url, function() { document.location = url; });
Code (CrumbleCookie from: http://www.dannytalk.com/read-google-analytics-cookie-script/)
/**
* Use this to log a pageview to google and make sure it gets through
* See: http://www.google.com/support/forum/p/Google%20Analytics/thread?tid=5f11a529100f1d47&hl=en
*/
function trackPageview(url, fn) {
var utmaCookie = crumbleCookie('__utma');
var utmzCookie = crumbleCookie('__utmz');
var cookies = '__utma=' + utmaCookie + ';__utmz=' + utmzCookie;
var requestId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var hId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var utmUrl = 'http://www.google-analytics.com/__utm.gif';
utmUrl += '?utmwv=4.8.9';
utmUrl += '&utmn=' + requestId;
utmUrl += '&utmhn=' + encodeURIComponent(window.location.hostname);
utmUrl += '&utmhid=' + hId;
utmUrl += '&utmr=-';
utmUrl += '&utmp=' + encodeURIComponent(url);
utmUrl += '&utmac=' + encodeURIComponent(_gaProfileId);
utmUrl += '&utmcc=' + encodeURIComponent(cookies);
var image = new Image();
image.onload = function() { fn(); };
image.src = utmUrl;
}
/**
* #author: Danny Ng (http://www.dannytalk.com/read-google-analytics-cookie-script/)
* #modified: 19/08/10
* #notes: Free to use and distribute without altering this comment. Would appreciate a link back :)
*/
// Strip leading and trailing white-space
String.prototype.trim = function() { return this.replace(/^\s*|\s*$/g, ''); }
// Check if string is empty
String.prototype.empty = function() {
if (this.length == 0)
return true;
else if (this.length > 0)
return /^\s*$/.test(this);
}
// Breaks cookie into an object of keypair cookie values
function crumbleCookie(c)
{
var cookie_array = document.cookie.split(';');
var keyvaluepair = {};
for (var cookie = 0; cookie < cookie_array.length; cookie++)
{
var key = cookie_array[cookie].substring(0, cookie_array[cookie].indexOf('=')).trim();
var value = cookie_array[cookie].substring(cookie_array[cookie].indexOf('=')+1, cookie_array[cookie].length).trim();
keyvaluepair[key] = value;
}
if (c)
return keyvaluepair[c] ? keyvaluepair[c] : null;
return keyvaluepair;
}
Using onmousedown instead of onclick may also help. It doesn't eliminate the race condition, but it gives GA a head start. There's also the concern of someone clicking on a link and dragging away before letting go of the mouse button, but that's probably a negligible case.