-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Lars Brillert
committed
Aug 25, 2014
1 parent
a36c214
commit a0c7849
Showing
16 changed files
with
487 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,4 @@ spec/reports | |
test/tmp | ||
test/version_tmp | ||
tmp | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,77 @@ | ||
require "rack" | ||
require "tilt" | ||
require "active_support/core_ext/hash" | ||
require "active_support/json" | ||
|
||
require "rack/tracker/version" | ||
require "rack/tracker/handler" | ||
require "rack/tracker/google_analytics/google_analytics" | ||
require "rack/tracker/facebook/facebook" | ||
|
||
module Rack | ||
module Tracker | ||
# Your code goes here... | ||
class Tracker | ||
EVENT_TRACKING_KEY = 'tracker' | ||
|
||
def initialize(app, &block) | ||
@app = app | ||
@handlers = Rack::Tracker::HandlerSet.new(&block) | ||
end | ||
|
||
def call(env) | ||
@status, @headers, @body = @app.call(env) | ||
return [@status, @headers, @body] unless html? | ||
response = Rack::Response.new([], @status, @headers) | ||
|
||
env[EVENT_TRACKING_KEY] = {} unless env[EVENT_TRACKING_KEY] | ||
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
session = env["rack.session"] | ||
if response.ok? | ||
# Write out the events now | ||
|
||
# Get any stored events from a redirection | ||
stored_events = session.delete(EVENT_TRACKING_KEY) if session | ||
|
||
env[EVENT_TRACKING_KEY].merge!(stored_events) unless stored_events.nil? | ||
elsif response.redirection? && session | ||
# Store the events until next time | ||
env["rack.session"][EVENT_TRACKING_KEY] = env[EVENT_TRACKING_KEY] | ||
end | ||
|
||
@body.each { |fragment| response.write inject(env, fragment) } | ||
@body.close if @body.respond_to?(:close) | ||
|
||
response.finish | ||
end | ||
|
||
private | ||
|
||
def html?; @headers['Content-Type'] =~ /html/; end | ||
|
||
def inject(env, response) | ||
@handler = @handlers.first.name.new(env, @handlers.first.options) | ||
response.gsub(%r{</head>}, @handler.render + "</head>") | ||
end | ||
|
||
class HandlerSet | ||
Handler = Struct.new(:name, :options) | ||
|
||
def initialize(&block) | ||
@handlers = [] | ||
self.instance_exec(&block) if block_given? | ||
end | ||
|
||
def handler(name, opts = {}, &block) | ||
@handlers << Handler.new(name, opts) | ||
end | ||
|
||
def first | ||
@handlers.first | ||
end | ||
|
||
def to_a | ||
@handlers | ||
end | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
class Rack::Tracker::Facebook < Rack::Tracker::Handler | ||
|
||
# options do | ||
# locale 'de_DE' | ||
# app_id | ||
# custom_audience_id | ||
# end | ||
|
||
# event do | ||
# id | ||
# value | ||
# currency | ||
# end | ||
|
||
# position :body | ||
|
||
def event | ||
env[:rack_tracker][:facebook][:event] rescue {} | ||
end | ||
|
||
def render | ||
Tilt.new( File.join( File.dirname(__FILE__), 'template/facebook.erb') ).render(self) | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<script type="text/javascript"> | ||
(function() { | ||
var _fbq = window._fbq || (window._fbq = []); | ||
if (!_fbq.loaded) { | ||
var fbds = document.createElement('script'); | ||
fbds.async = true; | ||
fbds.src = '//connect.facebook.net/en_US/fbds.js'; | ||
var s = document.getElementsByTagName('script')[0]; | ||
s.parentNode.insertBefore(fbds, s); | ||
_fbq.loaded = true; | ||
} | ||
})(); | ||
window._fbq = window._fbq || []; | ||
|
||
</script> | ||
|
||
<% if options[:custom_audience_id] %> | ||
<script type="text/javascript"> | ||
window._fbq.push(['addPixelId', '<%= options[:custom_audience_id] %>']); | ||
window._fbq.push(["track", "PixelInitialized", {}]); | ||
</script> | ||
|
||
<noscript> | ||
<img height="1" width="1" alt="" style="display:none" src="https://www.facebook.com/tr?id=<%= options[:custom_audience_id] %>&ev=PixelInitialized"> | ||
</noscript> | ||
<% end %> | ||
|
||
<% if event.any? %> | ||
<script type="text/javascript"> | ||
window._fbq.push(['track', '<%= event[:pixel_id] %>', {'value': '<%= event[:value] %>', 'currency': '<%= event[:currency] %>'}]); | ||
</script> | ||
|
||
<noscript> | ||
<img height="1" width="1" alt="" style="display:none" src="https://www.facebook.com/offsite_event.php?id=<%= event[:pixel_id] %>&value=<%= event[:value] %>&currency=<%= event[:currency] %>"> | ||
</noscript> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
class Rack::Tracker::GoogleAnalytics < Rack::Tracker::Handler | ||
class Event < Struct.new(:category, :action, :label, :value) | ||
def write | ||
{ hitType: 'event', eventCategory: self.category, eventAction: self.action, eventLabel: self.label, eventValue: self.value }.select{|k,v| v }.to_json | ||
end | ||
end | ||
|
||
def events | ||
env['tracker.google_analytics.events'] || [] | ||
end | ||
|
||
def tracker | ||
options[:tracker].try(:call, env) || options[:tracker] | ||
end | ||
|
||
def render | ||
Tilt.new( File.join( File.dirname(__FILE__), 'template/google_analytics.erb') ).render(self) | ||
end | ||
|
||
end |
43 changes: 43 additions & 0 deletions
43
lib/rack/tracker/google_analytics/template/google_analytics.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<script type="text/javascript"> | ||
|
||
<% if tracker %> | ||
(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', '<%= tracker %>', <%= options.slice(:cookieDomain).to_json %>); | ||
|
||
<% if options[:enhanced_link_attribution] %> | ||
ga('require', 'linkid', 'linkid.js'); | ||
<% end %> | ||
|
||
<% if options[:advertising] %> | ||
ga('require', 'displayfeatures'); | ||
<% end %> | ||
|
||
<% if options[:ecommerce] %> | ||
ga('require', 'ecommerce', 'ecommerce.js'); | ||
<% end %> | ||
|
||
<% if options[:anonymize_ip] %> | ||
ga('set', 'anonymizeIp', true); | ||
<% end %> | ||
|
||
<% if options[:adjusted_bounce_rate_timeouts] %> | ||
<% options[:adjusted_bounce_rate_timeouts].each do |timeout| %> | ||
setTimeout(ga('send', 'event', '<%= "#{timeout.to_s}_seconds" %>', 'read'),<%= timeout*1000 %>); | ||
<% end %> | ||
<% end %> | ||
|
||
<% end %> | ||
|
||
<% events.each do |var| %> | ||
ga('send', <%= var.write() %>); | ||
<% end %> | ||
|
||
<% if tracker %> | ||
ga('send', 'pageview'); | ||
<% end %> | ||
|
||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
class Rack::Tracker::Handler | ||
# options do | ||
# cookie_domain "foo" | ||
# end | ||
|
||
attr_accessor :options | ||
attr_accessor :env | ||
|
||
def initialize(env, options = {}) | ||
self.env = env | ||
self.options = options | ||
end | ||
|
||
def render | ||
raise ArgumentError.new('needs implementation') | ||
end | ||
end |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module Rack | ||
module Tracker | ||
class Tracker | ||
VERSION = "0.0.1" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<script type="text/javascript"> | ||
<% if dummy_alert %> | ||
alert('<%= dummy_alert %>'); | ||
<% else %> | ||
alert('this is a dummy class'); | ||
<% end %> | ||
|
||
console.log('<%= options[:foo] %>'); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
RSpec.describe Rack::Tracker::Facebook do | ||
def env | ||
{} | ||
end | ||
|
||
describe 'with custom audience id' do | ||
subject { described_class.new(env, custom_audience_id: 'custom_audience_id').render } | ||
|
||
it 'will push the tracking events to the queue' do | ||
expect(subject).to match(%r{window._fbq.push\(\['addPixelId', 'custom_audience_id'\]\)}) | ||
expect(subject).to match(%r{window._fbq.push\(\["track", "PixelInitialized", \{\}\]\)}) | ||
end | ||
|
||
it 'will add the noscript fallback' do | ||
expect(subject).to match(%r{https://www.facebook.com/tr\?id=custom_audience_id&ev=PixelInitialized}) | ||
end | ||
end | ||
|
||
describe 'with events' do | ||
def env | ||
{ | ||
rack_tracker: { | ||
facebook: { | ||
event: { | ||
pixel_id: '123456789', | ||
value: '23', | ||
currency: 'EUR', | ||
} | ||
} | ||
} | ||
} | ||
end | ||
subject { described_class.new(env).render } | ||
|
||
it 'will push the tracking events to the queue' do | ||
expect(subject).to match(%r{\['track', '123456789', \{'value': '23', 'currency': 'EUR'\}\]}) | ||
end | ||
|
||
it 'will add the noscript fallback' do | ||
expect(subject).to match(%r{https://www.facebook.com/offsite_event.php\?id=123456789&value=23&currency=EUR}) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
RSpec.describe Rack::Tracker::GoogleAnalytics do | ||
|
||
def env | ||
{misc: 'foobar'} | ||
end | ||
|
||
describe "with events" do | ||
describe "default" do | ||
def env | ||
{"tracker.google_analytics.events" => [ | ||
Rack::Tracker::GoogleAnalytics::Event.new("Users", "Login", "Standard") | ||
]} | ||
end | ||
|
||
subject { described_class.new(env, tracker: 'somebody', cookieDomain: "railslabs.com").render } | ||
it "will show events" do | ||
expect(subject).to match(%r{ga\('send', {\"hitType\":\"event\",\"eventCategory\":\"Users\",\"eventAction\":\"Login\",\"eventLabel\":\"Standard\"}\)}) | ||
end | ||
end | ||
|
||
describe "with a event value" do | ||
def env | ||
{"tracker.google_analytics.events" => [ | ||
Rack::Tracker::GoogleAnalytics::Event.new("Users", "Login", "Standard", 5) | ||
]} | ||
end | ||
|
||
subject { described_class.new(env, tracker: 'somebody', cookieDomain: "railslabs.com").render } | ||
it "will show events with values" do | ||
expect(subject).to match(%r{ga\('send', {\"hitType\":\"event\",\"eventCategory\":\"Users\",\"eventAction\":\"Login\",\"eventLabel\":\"Standard\",\"eventValue\":5}\)},) | ||
end | ||
end | ||
end | ||
|
||
describe "with custom domain" do | ||
subject { described_class.new(env, tracker: 'somebody', cookieDomain: "railslabs.com").render } | ||
|
||
it "will show asyncronous tracker with cookieDomain" do | ||
expect(subject).to match(%r{ga\('create', 'somebody', {\"cookieDomain\":\"railslabs.com\"}\)}) | ||
expect(subject).to match(%r{ga\('send', 'pageview'\)}) | ||
end | ||
end | ||
|
||
describe "with enhanced_link_attribution" do | ||
subject { described_class.new(env, tracker: 'happy', enhanced_link_attribution: true).render } | ||
|
||
it "will embedded the linkid plugin script" do | ||
expect(subject).to match(%r{linkid.js}) | ||
end | ||
end | ||
|
||
describe "with advertising" do | ||
subject { described_class.new(env, tracker: 'happy', advertising: true).render } | ||
|
||
it "will require displayfeatures" do | ||
expect(subject).to match(%r{ga\('require', 'displayfeatures'\)}) | ||
end | ||
end | ||
|
||
describe "with e-commerce" do | ||
subject { described_class.new(env, tracker: 'happy', ecommerce: true).render } | ||
|
||
it "will require the ecommerce plugin" do | ||
expect(subject).to match(%r{ga\('require', 'ecommerce', 'ecommerce\.js'\)}) | ||
end | ||
end | ||
|
||
describe "with anonymizeIp" do | ||
subject { described_class.new(env, tracker: 'happy', anonymize_ip: true).render } | ||
|
||
it "will set anonymizeIp to true" do | ||
expect(subject).to match(%r{ga\('set', 'anonymizeIp', true\)}) | ||
end | ||
end | ||
|
||
describe "with dynamic tracker" do | ||
subject { described_class.new(env, { tracker: lambda { |env| return env[:misc] }}).render } | ||
|
||
it 'will call tracker lambdas to obtain tracking codes' do | ||
expect(subject).to match(%r{ga\('create', 'foobar', {}\)}) | ||
end | ||
end | ||
|
||
describe 'adjusted bounce rate' do | ||
subject { described_class.new(env, tracker: 'afake', adjusted_bounce_rate_timeouts: [15, 30]).render } | ||
|
||
it "will add timeouts to push read events" do | ||
expect(subject).to match(%r{ga\('send', 'event', '15_seconds', 'read'\)}) | ||
expect(subject).to match(%r{ga\('send', 'event', '30_seconds', 'read'\)}) | ||
end | ||
end | ||
|
||
end |
Oops, something went wrong.
what about simply
env[EVENT_TRACKING_KEY] ||= {}
?