From 482e8644d84b04a39e203935ff7a474fd422d675 Mon Sep 17 00:00:00 2001 From: Max Goodman Date: Wed, 25 Jul 2012 18:43:21 -0700 Subject: [PATCH] Fire a pixel for UI flow tracking of sub/unsubscribe. This will allow us to collect information about how users found subreddits that they subscribe to. We can use this information to test the effectiveness of new ways of discovering and subscribing to subreddits. Specifically, when the subscribe button is clicked, for the current page and previous page: the URL, referrer URL, and the type of UI element clicked are sent. We'll use this to answer questions like: * "did clicking on gizmo A lead to users subscribing to subreddit B?" * "why did we see a spike in subscriptions to subreddit X today?" --- r2/example.ini | 2 + r2/r2/lib/template_helpers.py | 1 + r2/r2/public/static/js/analytics.js | 92 +++++++++++++++++++++++++++++ r2/r2/public/static/js/base.js | 3 + r2/r2/public/static/js/login.js | 4 +- r2/r2/public/static/js/reddit.js | 4 ++ r2/r2/public/static/js/utils.js | 27 +++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) 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(' ') } }