mixpanel 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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