mixpanel 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  Mixpanel is a real-time analytics service that helps companies understand how users interact with web applications.
4
4
  http://mixpanel.com
5
5
 
6
- == What does this Gem do?
6
+ == What does this Gem does?
7
7
 
8
8
  * Track events with properties directly from your backend.
9
9
  * Track events with properties through javascript using a rack middleware.
@@ -20,20 +20,20 @@ In your environment config file add this.
20
20
 
21
21
  Rails::Initializer.run do |config|
22
22
 
23
- config.middleware.use "MixpanelMiddleware", "YOUR_MIXPANEL_API_TOKEN"
23
+ config.middleware.use "Mixpanel::Tracker::Middleware", "YOUR_MIXPANEL_API_TOKEN"
24
24
 
25
25
  If you want to use the asynchronous version of Mixpanel's javascript API
26
26
 
27
27
  Rails::Initializer.run do |config|
28
28
 
29
- config.middleware.use "MixpanelMiddleware", "YOUR_MIXPANEL_API_TOKEN", :async => true
29
+ config.middleware.use "Mixpanel::Tracker::Middleware", "YOUR_MIXPANEL_API_TOKEN", :async => true
30
30
 
31
31
  In your application_controller class add a method to instance mixpanel.
32
32
 
33
33
  before_filter :initialize_mixpanel
34
34
 
35
35
  def initialize_mixpanel
36
- @mixpanel = Mixpanel.new("YOUR_MIXPANEL_API_TOKEN", request.env, true)
36
+ @mixpanel = Mixpanel::Tracker.new("YOUR_MIXPANEL_API_TOKEN", request.env, true)
37
37
  end
38
38
 
39
39
  Then in each request you want to track some event you can use:
@@ -54,10 +54,16 @@ To execute any javascript API call
54
54
 
55
55
  == Notes
56
56
 
57
- There are two forms of async operation:
57
+ There are two forms of async operation:
58
58
  * Using MixpanelMiddleware, events are queued via Mixpanel#append_event and inserted into a JavaScript block within the HTML response.
59
59
  * Using Mixpanel.new(…, …, true), events are sent to a subprocess via a pipe and the sub process which asynchronously send events to Mixpanel. This process uses a single thread to upload events, and may start dropping events if your application generates them at a very high rate.
60
60
 
61
+ == Deprecation Notes
62
+
63
+ For a short term this method will be accepted but it will be deprecated soon.
64
+
65
+ Mixpanel.new
66
+
61
67
  == Collaborators and Maintainers
62
68
 
63
69
  * {Alvaro Gil}[https://github.com/zevarito] (Author)
@@ -1,2 +1,8 @@
1
- require 'mixpanel/mixpanel'
2
- require 'mixpanel/mixpanel_middleware'
1
+ require 'mixpanel/tracker'
2
+
3
+ module Mixpanel
4
+ def self.new(token, env, async = false)
5
+ Kernel.warn("DEPRECATED: Use Mixpanel::Tracker.new instead")
6
+ Mixpanel::Tracker.new(token, env, async)
7
+ end
8
+ end
@@ -0,0 +1,97 @@
1
+ require "open-uri"
2
+ require 'base64'
3
+ require 'json'
4
+ require 'thread'
5
+ require 'mixpanel/tracker/middleware'
6
+
7
+ module Mixpanel
8
+ class Tracker
9
+ def initialize(token, env, async = false)
10
+ @token = token
11
+ @env = env
12
+ @async = async
13
+ clear_queue
14
+ end
15
+
16
+ def append_event(event, properties = {})
17
+ append_api('track', event, properties)
18
+ end
19
+
20
+ def append_api(type, *args)
21
+ queue << [type, args.map {|arg| arg.to_json}]
22
+ end
23
+
24
+ def track_event(event, properties = {})
25
+ params = build_event(event, properties.merge(:token => @token, :time => Time.now.utc.to_i, :ip => ip))
26
+ parse_response request(params)
27
+ end
28
+
29
+ def ip
30
+ @env.has_key?("REMOTE_ADDR") ? @env["REMOTE_ADDR"] : ""
31
+ end
32
+
33
+ def queue
34
+ @env["mixpanel_events"]
35
+ end
36
+
37
+ def clear_queue
38
+ @env["mixpanel_events"] = []
39
+ end
40
+
41
+ class <<self
42
+ WORKER_MUTEX = Mutex.new
43
+
44
+ def worker
45
+ WORKER_MUTEX.synchronize do
46
+ @worker || (@worker = IO.popen(self.cmd, 'w'))
47
+ end
48
+ end
49
+
50
+ def dispose_worker(w)
51
+ WORKER_MUTEX.synchronize do
52
+ if(@worker == w)
53
+ @worker = nil
54
+ w.close
55
+ end
56
+ end
57
+ end
58
+
59
+ def cmd
60
+ @cmd || begin
61
+ require 'escape'
62
+ require 'rbconfig'
63
+ interpreter = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
64
+ subprocess = File.join(File.dirname(__FILE__), 'tracker/subprocess.rb')
65
+ @cmd = Escape.shell_command([interpreter, subprocess])
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def parse_response(response)
73
+ response == "1" ? true : false
74
+ end
75
+
76
+ def request(params)
77
+ data = Base64.encode64(JSON.generate(params)).gsub(/\n/,'')
78
+ url = "http://api.mixpanel.com/track/?data=#{data}"
79
+
80
+ if(@async)
81
+ w = Tracker.worker
82
+ begin
83
+ url << "\n"
84
+ w.write(url)
85
+ rescue Errno::EPIPE => e
86
+ Tracker.dispose_worker(w)
87
+ end
88
+ else
89
+ open(url).read
90
+ end
91
+ end
92
+
93
+ def build_event(event, properties)
94
+ {:event => event, :properties => properties}
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,124 @@
1
+ require 'rack'
2
+
3
+ module Mixpanel
4
+ class Tracker
5
+ class Middleware
6
+ def initialize(app, mixpanel_token, options={})
7
+ @app = app
8
+ @token = mixpanel_token
9
+ @options = {
10
+ :async => false
11
+ }.merge(options)
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+
17
+ @status, @headers, @response = @app.call(env)
18
+
19
+ update_response!
20
+ update_content_length!
21
+ delete_event_queue!
22
+
23
+ [@status, @headers, @response]
24
+ end
25
+
26
+ private
27
+
28
+ def update_response!
29
+ @response.each do |part|
30
+ if is_regular_request? && is_html_response?
31
+ insert_at = part.index('</head')
32
+ unless insert_at.nil?
33
+ part.insert(insert_at, render_event_tracking_scripts) unless queue.empty?
34
+ part.insert(insert_at, render_mixpanel_scripts) #This will insert the mixpanel initialization code before the queue of tracking events.
35
+ end
36
+ elsif is_ajax_request? && is_html_response?
37
+ part.insert(0, render_event_tracking_scripts) unless queue.empty?
38
+ elsif is_ajax_request? && is_javascript_response?
39
+ part.insert(0, render_event_tracking_scripts(false)) unless queue.empty?
40
+ end
41
+ end
42
+ end
43
+
44
+ def update_content_length!
45
+ new_size = 0
46
+ @response.each{|part| new_size += part.bytesize}
47
+ @headers.merge!("Content-Length" => new_size.to_s)
48
+ end
49
+
50
+ def is_regular_request?
51
+ !is_ajax_request?
52
+ end
53
+
54
+ def is_ajax_request?
55
+ @env.has_key?("HTTP_X_REQUESTED_WITH") && @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
56
+ end
57
+
58
+ def is_html_response?
59
+ @headers["Content-Type"].include?("text/html") if @headers.has_key?("Content-Type")
60
+ end
61
+
62
+ def is_javascript_response?
63
+ @headers["Content-Type"].include?("text/javascript") if @headers.has_key?("Content-Type")
64
+ end
65
+
66
+ def render_mixpanel_scripts
67
+ if @options[:async]
68
+ <<-EOT
69
+ <script type='text/javascript'>
70
+ var mpq = [];
71
+ mpq.push(["init", "#{@token}"]);
72
+ (function() {
73
+ var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
74
+ mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
75
+ var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
76
+ })();
77
+ </script>
78
+ EOT
79
+ else
80
+ <<-EOT
81
+ <script type='text/javascript'>
82
+ var mp_protocol = (('https:' == document.location.protocol) ? 'https://' : 'http://');
83
+ document.write(unescape('%3Cscript src="' + mp_protocol + 'api.mixpanel.com/site_media/js/api/mixpanel.js" type="text/javascript"%3E%3C/script%3E'));
84
+ </script>
85
+ <script type='text/javascript'>
86
+ try {
87
+ var mpmetrics = new MixpanelLib('#{@token}');
88
+ } catch(err) {
89
+ null_fn = function () {};
90
+ var mpmetrics = {
91
+ track: null_fn, track_funnel: null_fn, register: null_fn, register_once: null_fn, register_funnel: null_fn
92
+ };
93
+ }
94
+ </script>
95
+ EOT
96
+ end
97
+ end
98
+
99
+ def delete_event_queue!
100
+ @env.delete('mixpanel_events')
101
+ end
102
+
103
+ def queue
104
+ return [] if !@env.has_key?('mixpanel_events') || @env['mixpanel_events'].empty?
105
+ @env['mixpanel_events']
106
+ end
107
+
108
+ def render_event_tracking_scripts(include_script_tag=true)
109
+ return "" if queue.empty?
110
+
111
+ if @options[:async]
112
+ output = queue.map {|type, arguments| %(mpq.push(["#{type}", #{arguments.join(', ')}]);) }.join("\n")
113
+ else
114
+ output = queue.map {|type, arguments| %(mpmetrics.#{type}(#{arguments.join(', ')});) }.join("\n")
115
+ end
116
+
117
+ output = "try {#{output}} catch(err) {}"
118
+
119
+ include_script_tag ? "<script type='text/javascript'>#{output}</script>" : output
120
+ end
121
+ end
122
+ end
123
+ end
124
+
@@ -0,0 +1,29 @@
1
+ require 'open-uri'
2
+ require 'thread'
3
+
4
+ module Mixpanel
5
+ class Tracker
6
+ class Subprocess
7
+ Q = Queue.new
8
+ ENDMARKER = Object.new
9
+
10
+ Thread.abort_on_exception = true
11
+ producer = Thread.new do
12
+ STDIN.each_line() do |url|
13
+ STDERR.puts("Dropped: #{url}") && next if Q.length > 10000
14
+ Q << url
15
+ end
16
+ Q << ENDMARKER
17
+ end
18
+
19
+ loop do
20
+ url = Q.pop
21
+ break if(url == ENDMARKER)
22
+ url.chomp!
23
+ next if(url.empty?) #for testing
24
+ open(url).read
25
+ end
26
+ producer.join
27
+ end
28
+ end
29
+ end
@@ -2,7 +2,7 @@ files = ['README.rdoc', 'LICENSE', 'Rakefile', 'mixpanel.gemspec', '{spec,lib}/*
2
2
 
3
3
  spec = Gem::Specification.new do |s|
4
4
  s.name = "mixpanel"
5
- s.version = "0.8.1"
5
+ s.version = "0.9.0"
6
6
  s.rubyforge_project = "mixpanel"
7
7
  s.description = "Simple lib to track events in Mixpanel service. It can be used in any rack based framework."
8
8
  s.author = "Alvaro Gil"
@@ -1,113 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mixpanel do
4
- before(:each) do
5
- @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"})
6
- end
7
-
8
- context "Initializing object" do
9
- it "should have an instance variable for token and events" do
10
- @mixpanel.instance_variables.should include("@token", "@env")
11
- end
12
- end
13
-
14
- context "Cleaning appended events" do
15
- it "should clear the queue" do
16
- @mixpanel.append_event("Sign up")
17
- @mixpanel.queue.size.should == 1
18
- @mixpanel.clear_queue
19
- @mixpanel.queue.size.should == 0
20
- end
21
- end
22
-
23
- context "Accessing Mixpanel through direct request" do
24
- context "Tracking events" do
25
- it "should track simple events" do
26
- @mixpanel.track_event("Sign up").should == true
27
- end
28
-
29
- it "should call request method with token and time value" do
30
- params = {:event => "Sign up", :properties => {:token => MIX_PANEL_TOKEN, :time => Time.now.utc.to_i, :ip => "127.0.0.1"}}
31
-
32
- @mixpanel.should_receive(:request).with(params).and_return("1")
33
- @mixpanel.track_event("Sign up").should == true
34
- end
35
- end
36
- end
37
-
38
- context "Accessing Mixpanel through javascript API" do
39
- context "Appending events" do
40
- it "should store the event under the appropriate key" do
41
- @mixpanel.append_event("Sign up")
42
- @env.has_key?("mixpanel_events").should == true
43
- end
44
-
45
- it "should be the same the queue than env['mixpanel_events']" do
46
- @env['mixpanel_events'].object_id.should == @mixpanel.queue.object_id
47
- end
48
-
49
- it "should append simple events" do
50
- @mixpanel.append_event("Sign up")
51
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {})
52
- end
53
-
54
- it "should append events with properties" do
55
- @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
56
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
57
- end
58
-
59
- it "should give direct access to queue" do
60
- @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
61
- @mixpanel.queue.size.should == 1
62
- end
63
-
64
- it "should provide direct access to the JS api" do
65
- @mixpanel.append_api('track', "Sign up", {:referer => 'http://example.com'})
66
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
67
- end
68
-
69
- it "should allow identify to be called through the JS api" do
70
- @mixpanel.append_api('identify', "some@one.com")
71
- mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
72
- end
73
-
74
- it "should allow identify to be called through the JS api" do
75
- @mixpanel.append_api('identify', "some@one.com")
76
- mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
77
- end
78
-
79
- it "should allow the tracking of super properties in JS" do
80
- @mixpanel.append_api('register', {:user_id => 12345, :email => "some@one.com"})
81
- mixpanel_queue_should_include(@mixpanel, 'register', {:user_id => 12345, :email => "some@one.com"})
82
- end
83
- end
84
- end
85
-
86
- context "Accessing Mixpanel asynchronously" do
87
- it "should open a subprocess successfully" do
88
- w = Mixpanel.worker
89
- w.should == Mixpanel.worker
90
- end
91
-
92
- it "should be able to write lines to the worker" do
93
- w = Mixpanel.worker
94
-
95
- #On most systems this will exceed the pipe buffer size
96
- 8.times do
97
- 9000.times do
98
- w.write("\n")
99
- end
100
- sleep 0.1
101
- end
102
- end
103
-
104
- it "should dispose of a worker" do
105
- w = Mixpanel.worker
106
- Mixpanel.dispose_worker(w)
107
-
108
- w.closed?.should == true
109
- w2 = Mixpanel.worker
110
- w2.should_not == w
4
+ context "Deprecated initialization mode" do
5
+ it "should instantiate the object as it was doing before but drop a deprecation warning" do
6
+ mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"})
7
+ mixpanel.should be_kind_of(Mixpanel::Tracker)
111
8
  end
112
9
  end
113
10
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MixpanelMiddleware do
3
+ describe Mixpanel::Tracker::Middleware do
4
4
  include Rack::Test::Methods
5
5
 
6
6
  describe "Dummy apps, no text/html" do
@@ -136,7 +136,7 @@ describe MixpanelMiddleware do
136
136
 
137
137
  describe "Tracking async appended events" do
138
138
  before do
139
- @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, {})
139
+ @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, {})
140
140
  @mixpanel.append_event("Visit", {:article => 1})
141
141
  @mixpanel.append_event("Sign in")
142
142
  end
@@ -209,7 +209,7 @@ describe MixpanelMiddleware do
209
209
 
210
210
  describe "Tracking appended events" do
211
211
  before do
212
- @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, {})
212
+ @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, {})
213
213
  @mixpanel.append_event("Visit", {:article => 1})
214
214
  @mixpanel.append_event("Sign in")
215
215
  end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mixpanel::Tracker do
4
+ before(:each) do
5
+ @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"})
6
+ end
7
+
8
+ context "Initializing object" do
9
+ it "should have an instance variable for token and events" do
10
+ @mixpanel.instance_variables.should include("@token", "@env")
11
+ end
12
+ end
13
+
14
+ context "Cleaning appended events" do
15
+ it "should clear the queue" do
16
+ @mixpanel.append_event("Sign up")
17
+ @mixpanel.queue.size.should == 1
18
+ @mixpanel.clear_queue
19
+ @mixpanel.queue.size.should == 0
20
+ end
21
+ end
22
+
23
+ context "Accessing Mixpanel through direct request" do
24
+ context "Tracking events" do
25
+ it "should track simple events" do
26
+ @mixpanel.track_event("Sign up").should == true
27
+ end
28
+
29
+ it "should call request method with token and time value" do
30
+ params = {:event => "Sign up", :properties => {:token => MIX_PANEL_TOKEN, :time => Time.now.utc.to_i, :ip => "127.0.0.1"}}
31
+
32
+ @mixpanel.should_receive(:request).with(params).and_return("1")
33
+ @mixpanel.track_event("Sign up").should == true
34
+ end
35
+ end
36
+ end
37
+
38
+ context "Accessing Mixpanel through javascript API" do
39
+ context "Appending events" do
40
+ it "should store the event under the appropriate key" do
41
+ @mixpanel.append_event("Sign up")
42
+ @env.has_key?("mixpanel_events").should == true
43
+ end
44
+
45
+ it "should be the same the queue than env['mixpanel_events']" do
46
+ @env['mixpanel_events'].object_id.should == @mixpanel.queue.object_id
47
+ end
48
+
49
+ it "should append simple events" do
50
+ @mixpanel.append_event("Sign up")
51
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {})
52
+ end
53
+
54
+ it "should append events with properties" do
55
+ @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
56
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
57
+ end
58
+
59
+ it "should give direct access to queue" do
60
+ @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
61
+ @mixpanel.queue.size.should == 1
62
+ end
63
+
64
+ it "should provide direct access to the JS api" do
65
+ @mixpanel.append_api('track', "Sign up", {:referer => 'http://example.com'})
66
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
67
+ end
68
+
69
+ it "should allow identify to be called through the JS api" do
70
+ @mixpanel.append_api('identify', "some@one.com")
71
+ mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
72
+ end
73
+
74
+ it "should allow identify to be called through the JS api" do
75
+ @mixpanel.append_api('identify', "some@one.com")
76
+ mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
77
+ end
78
+
79
+ it "should allow the tracking of super properties in JS" do
80
+ @mixpanel.append_api('register', {:user_id => 12345, :email => "some@one.com"})
81
+ mixpanel_queue_should_include(@mixpanel, 'register', {:user_id => 12345, :email => "some@one.com"})
82
+ end
83
+ end
84
+ end
85
+
86
+ context "Accessing Mixpanel asynchronously" do
87
+ it "should open a subprocess successfully" do
88
+ w = Mixpanel::Tracker.worker
89
+ w.should == Mixpanel::Tracker.worker
90
+ end
91
+
92
+ it "should be able to write lines to the worker" do
93
+ w = Mixpanel::Tracker.worker
94
+
95
+ #On most systems this will exceed the pipe buffer size
96
+ 8.times do
97
+ 9000.times do
98
+ w.write("\n")
99
+ end
100
+ sleep 0.1
101
+ end
102
+ end
103
+
104
+ it "should dispose of a worker" do
105
+ w = Mixpanel::Tracker.worker
106
+ Mixpanel::Tracker.dispose_worker(w)
107
+
108
+ w.closed?.should == true
109
+ w2 = Mixpanel::Tracker.worker
110
+ w2.should_not == w
111
+ end
112
+ end
113
+ end
@@ -1,5 +1,5 @@
1
1
  def setup_rack_application(application, options = {}, mixpanel_options = {})
2
- stub!(:app).and_return(MixpanelMiddleware.new(application.new(options), MIX_PANEL_TOKEN, mixpanel_options))
2
+ stub!(:app).and_return(Mixpanel::Tracker::Middleware.new(application.new(options), MIX_PANEL_TOKEN, mixpanel_options))
3
3
  end
4
4
 
5
5
  def html_document
@@ -200,4 +200,4 @@ def large_script
200
200
  </form>
201
201
  </div>
202
202
  EOT
203
- end
203
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixpanel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 61
4
+ hash: 59
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 8
9
- - 1
10
- version: 0.8.1
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alvaro Gil
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-31 00:00:00 -02:00
18
+ date: 2011-06-20 00:00:00 -03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -129,13 +129,14 @@ files:
129
129
  - LICENSE
130
130
  - Rakefile
131
131
  - mixpanel.gemspec
132
- - spec/mixpanel/mixpanel_middleware_spec.rb
133
132
  - spec/mixpanel/mixpanel_spec.rb
133
+ - spec/mixpanel/tracker/middleware_spec.rb
134
+ - spec/mixpanel/tracker_spec.rb
134
135
  - spec/spec_helper.rb
135
136
  - spec/support/rack_apps.rb
136
- - lib/mixpanel/mixpanel.rb
137
- - lib/mixpanel/mixpanel_middleware.rb
138
- - lib/mixpanel/mixpanel_subprocess.rb
137
+ - lib/mixpanel/tracker/middleware.rb
138
+ - lib/mixpanel/tracker/subprocess.rb
139
+ - lib/mixpanel/tracker.rb
139
140
  - lib/mixpanel.rb
140
141
  has_rdoc: true
141
142
  homepage: http://cuboxsa.com
@@ -1,95 +0,0 @@
1
- require "open-uri"
2
- require 'base64'
3
- require 'json'
4
-
5
- require 'thread'
6
-
7
- class Mixpanel
8
- def initialize(token, env, async = false)
9
- @token = token
10
- @env = env
11
- @async = async
12
- clear_queue
13
- end
14
-
15
- def append_event(event, properties = {})
16
- append_api('track', event, properties)
17
- end
18
-
19
- def append_api(type, *args)
20
- queue << [type, args.map {|arg| arg.to_json}]
21
- end
22
-
23
- def track_event(event, properties = {})
24
- params = build_event(event, properties.merge(:token => @token, :time => Time.now.utc.to_i, :ip => ip))
25
- parse_response request(params)
26
- end
27
-
28
- def ip
29
- @env.has_key?("REMOTE_ADDR") ? @env["REMOTE_ADDR"] : ""
30
- end
31
-
32
- def queue
33
- @env["mixpanel_events"]
34
- end
35
-
36
- def clear_queue
37
- @env["mixpanel_events"] = []
38
- end
39
-
40
- class <<self
41
- WORKER_MUTEX = Mutex.new
42
-
43
- def worker
44
- WORKER_MUTEX.synchronize do
45
- @worker || (@worker = IO.popen(self.cmd, 'w'))
46
- end
47
- end
48
-
49
- def dispose_worker(w)
50
- WORKER_MUTEX.synchronize do
51
- if(@worker == w)
52
- @worker = nil
53
- w.close
54
- end
55
- end
56
- end
57
-
58
- def cmd
59
- @cmd || begin
60
- require 'escape'
61
- require 'rbconfig'
62
- interpreter = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
63
- subprocess = File.join(File.dirname(__FILE__), 'mixpanel_subprocess.rb')
64
- @cmd = Escape.shell_command([interpreter, subprocess])
65
- end
66
- end
67
- end
68
-
69
- private
70
-
71
- def parse_response(response)
72
- response == "1" ? true : false
73
- end
74
-
75
- def request(params)
76
- data = Base64.encode64(JSON.generate(params)).gsub(/\n/,'')
77
- url = "http://api.mixpanel.com/track/?data=#{data}"
78
-
79
- if(@async)
80
- w = Mixpanel.worker
81
- begin
82
- url << "\n"
83
- w.write(url)
84
- rescue Errno::EPIPE => e
85
- Mixpanel.dispose_worker(w)
86
- end
87
- else
88
- open(url).read
89
- end
90
- end
91
-
92
- def build_event(event, properties)
93
- {:event => event, :properties => properties}
94
- end
95
- end
@@ -1,119 +0,0 @@
1
- require 'rack'
2
-
3
- class MixpanelMiddleware
4
- def initialize(app, mixpanel_token, options={})
5
- @app = app
6
- @token = mixpanel_token
7
- @options = {
8
- :async => false
9
- }.merge(options)
10
- end
11
-
12
- def call(env)
13
- @env = env
14
-
15
- @status, @headers, @response = @app.call(env)
16
-
17
- update_response!
18
- update_content_length!
19
- delete_event_queue!
20
-
21
- [@status, @headers, @response]
22
- end
23
-
24
- private
25
-
26
- def update_response!
27
- @response.each do |part|
28
- if is_regular_request? && is_html_response?
29
- insert_at = part.index('</head')
30
- unless insert_at.nil?
31
- part.insert(insert_at, render_event_tracking_scripts) unless queue.empty?
32
- part.insert(insert_at, render_mixpanel_scripts) #This will insert the mixpanel initialization code before the queue of tracking events.
33
- end
34
- elsif is_ajax_request? && is_html_response?
35
- part.insert(0, render_event_tracking_scripts) unless queue.empty?
36
- elsif is_ajax_request? && is_javascript_response?
37
- part.insert(0, render_event_tracking_scripts(false)) unless queue.empty?
38
- end
39
- end
40
- end
41
-
42
- def update_content_length!
43
- new_size = 0
44
- @response.each{|part| new_size += part.bytesize}
45
- @headers.merge!("Content-Length" => new_size.to_s)
46
- end
47
-
48
- def is_regular_request?
49
- !is_ajax_request?
50
- end
51
-
52
- def is_ajax_request?
53
- @env.has_key?("HTTP_X_REQUESTED_WITH") && @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
54
- end
55
-
56
- def is_html_response?
57
- @headers["Content-Type"].include?("text/html") if @headers.has_key?("Content-Type")
58
- end
59
-
60
- def is_javascript_response?
61
- @headers["Content-Type"].include?("text/javascript") if @headers.has_key?("Content-Type")
62
- end
63
-
64
- def render_mixpanel_scripts
65
- if @options[:async]
66
- <<-EOT
67
- <script type='text/javascript'>
68
- var mpq = [];
69
- mpq.push(["init", "#{@token}"]);
70
- (function() {
71
- var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
72
- mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
73
- var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
74
- })();
75
- </script>
76
- EOT
77
- else
78
- <<-EOT
79
- <script type='text/javascript'>
80
- var mp_protocol = (('https:' == document.location.protocol) ? 'https://' : 'http://');
81
- document.write(unescape('%3Cscript src="' + mp_protocol + 'api.mixpanel.com/site_media/js/api/mixpanel.js" type="text/javascript"%3E%3C/script%3E'));
82
- </script>
83
- <script type='text/javascript'>
84
- try {
85
- var mpmetrics = new MixpanelLib('#{@token}');
86
- } catch(err) {
87
- null_fn = function () {};
88
- var mpmetrics = {
89
- track: null_fn, track_funnel: null_fn, register: null_fn, register_once: null_fn, register_funnel: null_fn
90
- };
91
- }
92
- </script>
93
- EOT
94
- end
95
- end
96
-
97
- def delete_event_queue!
98
- @env.delete('mixpanel_events')
99
- end
100
-
101
- def queue
102
- return [] if !@env.has_key?('mixpanel_events') || @env['mixpanel_events'].empty?
103
- @env['mixpanel_events']
104
- end
105
-
106
- def render_event_tracking_scripts(include_script_tag=true)
107
- return "" if queue.empty?
108
-
109
- if @options[:async]
110
- output = queue.map {|type, arguments| %(mpq.push(["#{type}", #{arguments.join(', ')}]);) }.join("\n")
111
- else
112
- output = queue.map {|type, arguments| %(mpmetrics.#{type}(#{arguments.join(', ')});) }.join("\n")
113
- end
114
-
115
- output = "try {#{output}} catch(err) {}"
116
-
117
- include_script_tag ? "<script type='text/javascript'>#{output}</script>" : output
118
- end
119
- end
@@ -1,30 +0,0 @@
1
-
2
- require 'rubygems'
3
- require 'mixpanel'
4
- require 'open-uri'
5
-
6
- require 'thread'
7
-
8
- class Mixpanel::Subprocess
9
- Q = Queue.new
10
- ENDMARKER = Object.new
11
-
12
- Thread.abort_on_exception = true
13
- producer = Thread.new do
14
- STDIN.each_line() do |url|
15
- STDERR.puts("Dropped: #{url}") && next if Q.length > 10000
16
- Q << url
17
- end
18
- Q << ENDMARKER
19
- end
20
-
21
- loop do
22
- url = Q.pop
23
- break if(url == ENDMARKER)
24
- url.chomp!
25
- next if(url.empty?) #for testing
26
-
27
- open(url).read
28
- end
29
- producer.join
30
- end