diff --git a/r2/example.ini b/r2/example.ini index 10e631e49..e7c0db3e0 100644 --- a/r2/example.ini +++ b/r2/example.ini @@ -172,6 +172,8 @@ adtracker_url = /static/pixel.png adframetracker_url = /static/pixel.png # open redirector to bounce clicks off of on sponsored links for tracking clicktracker_url = /static/pixel.png +# url to request to track interaction statistics +uitracker_url = /static/pixel.png # new pixel newtracker_url = diff --git a/r2/r2/lib/template_helpers.py b/r2/r2/lib/template_helpers.py index bd613ba1e..f393c37c1 100755 --- a/r2/r2/lib/template_helpers.py +++ b/r2/r2/lib/template_helpers.py @@ -145,6 +145,7 @@ def js_config(): "tracking_domain": g.tracking_domain, "adtracker_url": g.adtracker_url, "clicktracker_url": g.clicktracker_url, + "uitracker_url": g.uitracker_url, "static_root": static(''), } return config diff --git a/r2/r2/public/static/js/analytics.js b/r2/r2/public/static/js/analytics.js index 8e2c0e304..680f2c887 100644 --- a/r2/r2/public/static/js/analytics.js +++ b/r2/r2/public/static/js/analytics.js @@ -95,5 +95,97 @@ r.analytics = { thumb.attr('href', click_url) thing.data('trackerFired', true) + }, + + fireUITrackingPixel: function(action, srname) { + var pixel = new Image() + pixel.src = r.config.uitracker_url + '?' + $.param( + _.extend( + { + 'act': action, + 'sr': srname, + 'r': Math.round(Math.random() * 2147483647) // cachebuster + }, + r.analytics.breadcrumbs.toParams() + ) + ) + } +} + +r.analytics.breadcrumbs = { + hasSessionStorage: 'sessionStorage' in window, + selector: '.thing, .side, .sr-list, .srdrop, .tagline, .md, .organic-listing, .gadget, a, button, input', + + init: function() { + this.data = this._load() + + var refreshed = this.data[0] && this.data[0]['url'] == window.location + if (!refreshed) { + this._storeBreadcrumb() + } + + $(document).delegate('a, button', 'click', $.proxy(function(ev) { + this.storeLastClick($(ev.target)) + }, this)) + }, + + _load: function() { + if (!this.hasSessionStorage) { + return [{stored: false}] + } + + var data + try { + data = JSON.parse(sessionStorage['breadcrumbs']) + } catch (e) { + data = [] + } + + if (!_.isArray(data)) { + data = [] + } + + return data + }, + + store: function(data) { + if (this.hasSessionStorage) { + sessionStorage['breadcrumbs'] = JSON.stringify(this.data) + } + }, + + _storeBreadcrumb: function() { + var cur = { + 'url': location.toString() + } + + if ('referrer' in document) { + var referrerExternal = !document.referrer.match('^' + r.config.currentOrigin), + referrerUnexpected = this.data[0] && document.referrer != this.data[0]['url'] + + if (referrerExternal || referrerUnexpected) { + cur['ref'] = document.referrer + } + } + + this.data.unshift(cur) + this.data = this.data.slice(0, 2) + this.store() + }, + + storeLastClick: function(el) { + this.data[0]['click'] = + r.utils.querySelectorFromEl(el, this.selector) + this.store() + }, + + toParams: function() { + params = [] + for (var i = 0; i < this.data.length; i++) { + _.each(this.data[i], function(v, k) { + params['c'+i+'_'+k] = v + }) + } + return params } } diff --git a/r2/r2/public/static/js/base.js b/r2/r2/public/static/js/base.js index 3479f5c81..32744baee 100644 --- a/r2/r2/public/static/js/base.js +++ b/r2/r2/public/static/js/base.js @@ -4,6 +4,9 @@ r.setup = function(config) { r.config = config // Set the legacy config global reddit = config + + r.config.currentOrigin = location.protocol+'//'+location.host + r.analytics.breadcrumbs.init() } $(function() { diff --git a/r2/r2/public/static/js/login.js b/r2/r2/public/static/js/login.js index 53b0552c3..f82829d7b 100644 --- a/r2/r2/public/static/js/login.js +++ b/r2/r2/public/static/js/login.js @@ -12,7 +12,7 @@ r.login = { endpoint = r.config.https_endpoint || ('http://'+r.config.ajax_domain), apiTarget = endpoint+'/api/'+action+'/'+username - if (this.currentOrigin == endpoint || $.support.cors) { + if (r.config.currentOrigin == endpoint || $.support.cors) { var params = form.serialize() params.push({name:'api_type', value:'json'}) $.ajax({ @@ -192,7 +192,7 @@ r.ui.LoginForm.prototype = $.extend(new r.ui.Form(), { _handleNetError: function(result, err, xhr) { r.ui.Form.prototype._handleNetError.apply(this, arguments) - if (xhr.status == 0 && r.login.currentOrigin != r.config.https_endpoint) { + if (xhr.status == 0 && r.config.currentOrigin != r.config.https_endpoint) { $('

').append( $('') .text(r.strings.login_fallback_msg) diff --git a/r2/r2/public/static/js/reddit.js b/r2/r2/public/static/js/reddit.js index d01343957..712b22acf 100644 --- a/r2/r2/public/static/js/reddit.js +++ b/r2/r2/public/static/js/reddit.js @@ -230,6 +230,8 @@ function toggle_label (elem, callback, cancelback) { } function toggle(elem, callback, cancelback) { + r.analytics.breadcrumbs.storeLastClick(elem) + var self = $(elem).parent().andSelf().filter(".option"); var sibling = self.removeClass("active") .siblings().addClass("active").get(0); @@ -332,6 +334,7 @@ function subscribe(reddit_name) { } $.things(reddit_name).find(".entry").addClass("likes"); $.request("subscribe", {sr: reddit_name, action: "sub"}); + r.analytics.fireUITrackingPixel("sub", reddit_name) } }; }; @@ -344,6 +347,7 @@ function unsubscribe(reddit_name) { } $.things(reddit_name).find(".entry").removeClass("likes"); $.request("subscribe", {sr: reddit_name, action: "unsub"}); + r.analytics.fireUITrackingPixel("unsub", reddit_name) } }; }; diff --git a/r2/r2/public/static/js/utils.js b/r2/r2/public/static/js/utils.js index 5c850cce4..9f9fae4ce 100644 --- a/r2/r2/public/static/js/utils.js +++ b/r2/r2/public/static/js/utils.js @@ -1,5 +1,32 @@ r.utils = { staticURL: function (item) { return r.config.static_root + '/' + item + }, + + querySelectorFromEl: function(targetEl, selector) { + return $(targetEl).parents().andSelf() + .filter(selector || '*') + .map(function(idx, el) { + var parts = [], + $el = $(el), + elFullname = $el.data('fullname'), + elId = $el.attr('id'), + elClass = $el.attr('class') + + parts.push(el.nodeName.toLowerCase()) + + if (elFullname) { + parts.push('[data-fullname="' + elFullname + '"]') + } else { + if (elId) { + parts.push('#' + elId) + } else if (elClass) { + parts.push('.' + _.compact(elClass.split(/\s+/)).join('.')) + } + } + + return parts.join('') + }) + .toArray().join(' ') } }