mixpanel 2.2.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ module Mixpanel::Person
2
+ PERSON_PROPERTIES = %w{email created first_name last_name last_login username country_code}
3
+ PERSON_URL = 'http://api.mixpanel.com/engage/'
4
+
5
+ def set(distinct_id, properties={}, options={})
6
+ engage :set, distinct_id, properties, options
7
+ end
8
+
9
+ def increment(distinct_id, properties={}, options={})
10
+ engage :add, distinct_id, properties, options
11
+ end
12
+
13
+ def append_set(properties={})
14
+ append 'people.set', properties_hash(properties, PERSON_PROPERTIES)
15
+ end
16
+
17
+ def append_increment(property, increment=1)
18
+ append 'people.increment', property, increment
19
+ end
20
+
21
+ def append_register(properties={})
22
+ append 'register', properties_hash(properties, PERSON_PROPERTIES)
23
+ end
24
+
25
+ def append_identify(distinct_id)
26
+ append 'identify', distinct_id
27
+ end
28
+
29
+ protected
30
+
31
+ def engage(action, distinct_id, properties, options)
32
+ options.reverse_merge! :async => @async, :url => PERSON_URL
33
+ data = build_person action, distinct_id, properties
34
+ url = "#{options[:url]}?data=#{encoded_data(data)}"
35
+ parse_response request(url, options[:async])
36
+ end
37
+
38
+ def build_person(action, distinct_id, properties)
39
+ { "$#{action}".to_sym => properties_hash(properties, PERSON_PROPERTIES), :$token => @token, :$distinct_id => distinct_id }
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ require 'open-uri'
2
+ require 'thread'
3
+
4
+ module Mixpanel
5
+ class Subprocess
6
+ Q = Queue.new
7
+ ENDMARKER = Object.new
8
+
9
+ Thread.abort_on_exception = true
10
+ producer = Thread.new do
11
+ STDIN.each_line() do |url|
12
+ STDERR.puts("Dropped: #{url}") && next if Q.length > 10000
13
+ Q << url
14
+ end
15
+ Q << ENDMARKER
16
+ end
17
+
18
+ loop do
19
+ url = Q.pop
20
+ break if(url == ENDMARKER)
21
+ url.chomp!
22
+ next if(url.empty?) #for testing
23
+ open(url).read
24
+ end
25
+ producer.join
26
+ end
27
+ end
@@ -2,171 +2,74 @@ require "open-uri"
2
2
  require 'base64'
3
3
  require 'json'
4
4
  require 'thread'
5
- require 'mixpanel/tracker/middleware'
6
5
 
7
6
  module Mixpanel
8
7
  class Tracker
8
+ require 'mixpanel/async'
9
+ require 'mixpanel/event'
10
+ require 'mixpanel/person'
11
+
12
+ extend Mixpanel::Async
13
+ include Mixpanel::Event
14
+ include Mixpanel::Person
15
+
16
+ def initialize(token, options={})
17
+ @token = token
18
+ @async = !!options.fetch(:async, false)
19
+ @persist = !!options.fetch(:persist, false)
20
+ @env = options.fetch :env, {}
21
+ @api_key = options.fetch :api_key, nil
9
22
 
10
- MIXPANEL_API_URL = 'http://api.mixpanel.com'.freeze
11
- TRACK_ENDPOINT = '/track/?data='.freeze
12
- ENGAGE_ENDPOINT = '/engage/?data='.freeze
13
- IMPORT_ENDPOINT = '/import/?data='.freeze
14
-
15
- PERSON_PROPERTIES = %w(email first_name last_name created last_login username country_code).freeze
16
-
17
- def initialize(token, env, options = {})
18
- @token = token
19
- @api_key = options.fetch(:api_key, "")
20
- @env = env
21
- @async = options.fetch(:async, false)
22
- @import = options.fetch(:import, false)
23
- @url = options.fetch(:url, MIXPANEL_API_URL)
24
- @persist = options.fetch(:persist, false)
25
-
23
+ # Make sure queue object is instantiated to an array. If not persisted, set queue object to empty array.
26
24
  if @persist
27
- @env["rack.session"]["mixpanel_events"] ||= []
28
- else
29
- clear_queue
30
- end
31
- end
32
-
33
- def append_event(event, properties = {})
34
- append_api('track', event, properties)
35
- end
36
-
37
- def append_person_event(properties = {})
38
- append_api('people.set', person_properties(properties))
39
- end
40
-
41
- def append_person_increment_event(property, increment=1)
42
- append_api('people.increment', property, increment)
43
- end
44
-
45
- def append_api(type, *args)
46
- queue << [type, args.map {|arg| arg.to_json}]
47
- end
48
-
49
- def track_event(event, properties = {})
50
- options = { :time => Time.now.utc.to_i, :ip => ip }
51
- options.merge!( :token => @token ) if @token
52
- parse_response request(:track,
53
- :event => event,
54
- :properties => options.merge(properties)
55
- )
56
- end
57
-
58
- def engage(action, distinct_id, properties = {})
59
- options = { }
60
- options.merge!( :$token => @token ) if @token
61
- parse_response request(:engage, options.merge(
62
- :$distinct_id => distinct_id,
63
- "$#{action}".to_sym => person_properties(properties)
64
- ))
65
- end
66
-
67
- def engage_set(distinct_id, properties = {})
68
- engage(:set, distinct_id, properties)
69
- end
70
-
71
- def engage_add(distinct_id, properties = {})
72
- engage(:add, distinct_id, properties)
73
- end
74
-
75
- def ip
76
- if @env.has_key?("HTTP_X_FORWARDED_FOR")
77
- @env["HTTP_X_FORWARDED_FOR"].split(",").last
78
- elsif @env.has_key?("REMOTE_ADDR")
79
- @env["REMOTE_ADDR"]
25
+ @env['rack.session'] ||= {}
26
+ @env['rack.session']['mixpanel_events'] ||= []
80
27
  else
81
- ""
28
+ @env['mixpanel_events'] = []
82
29
  end
83
30
  end
84
-
31
+
85
32
  def queue
86
- if @persist
87
- return @env["rack.session"]["mixpanel_events"]
88
- else
89
- return @env["mixpanel_events"]
33
+ @persist ? @env['rack.session']['mixpanel_events'] : @env['mixpanel_events']
34
+ end
35
+
36
+ def append(type, *args)
37
+ queue << [type, args.collect {|arg| arg.to_json}]
38
+ end
39
+
40
+ protected
41
+
42
+ # Walk through each property and see if it is in the special_properties. If so, change the key to have a $ in front of it.
43
+ def properties_hash(properties, special_properties)
44
+ properties.inject({}) do |props, (key, value)|
45
+ key = "$#{key}" if special_properties.include?(key.to_s)
46
+ props[key.to_sym] = value
47
+ props
90
48
  end
91
49
  end
92
-
93
- def clear_queue
94
- if @persist
95
- @env["rack.session"]["mixpanel_events"] = []
96
- else
97
- @env["mixpanel_events"] = []
98
- end
50
+
51
+ def encoded_data(parameters)
52
+ Base64.encode64(JSON.generate(parameters)).gsub(/\n/,'')
99
53
  end
100
-
101
- class << self
102
- WORKER_MUTEX = Mutex.new
103
-
104
- def worker
105
- WORKER_MUTEX.synchronize do
106
- @worker || (@worker = IO.popen(self.cmd, 'w'))
107
- end
108
- end
109
-
110
- def dispose_worker(w)
111
- WORKER_MUTEX.synchronize do
112
- if(@worker == w)
113
- @worker = nil
114
- w.close
115
- end
116
- end
117
- end
118
-
119
- def cmd
120
- @cmd || begin
121
- require 'escape'
122
- require 'rbconfig'
123
- interpreter = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
124
- subprocess = File.join(File.dirname(__FILE__), 'tracker/subprocess.rb')
125
- @cmd = Escape.shell_command([interpreter, subprocess])
126
- end
127
- end
128
- end
129
-
130
- private
131
-
132
- def person_properties(properties = {})
133
- properties.inject({}) do |out, (k, v)|
134
- if PERSON_PROPERTIES.member?(k.to_s)
135
- out["$#{k}".to_sym] = v
136
- else
137
- out[k] = v
138
- end
139
- out
140
- end
54
+
55
+ def request(url, async)
56
+ async ? send_async(url) : open(url).read
141
57
  end
142
-
58
+
143
59
  def parse_response(response)
144
- response == "1" ? true : false
145
- end
146
-
147
- def request(mode, params)
148
- data = Base64.encode64(JSON.generate(params)).gsub(/\n/,'')
149
-
150
- mode = :import if @import
151
- endpoint = case mode
152
- when :track then TRACK_ENDPOINT
153
- when :engage then ENGAGE_ENDPOINT
154
- when :import then IMPORT_ENDPOINT
155
- end
156
- url = "#{@url}#{endpoint}#{data}"
157
- url += "&api_key=#{@api_key}" if mode == :import
158
-
159
- if(@async)
160
- w = Tracker.worker
161
- begin
162
- url << "\n"
163
- w.write(url)
164
- rescue Errno::EPIPE => e
165
- Tracker.dispose_worker(w)
166
- end
167
- else
168
- open(url).read
60
+ response.to_i == 1
61
+ end
62
+
63
+ def send_async(url)
64
+ w = Mixpanel::Tracker.worker
65
+ begin
66
+ url << "\n"
67
+ w.write url
68
+ 1
69
+ rescue Errno::EPIPE => e
70
+ Mixpanel::Tracker.dispose_worker w
71
+ 0
169
72
  end
170
73
  end
171
74
  end
172
- end
75
+ end
data/lib/mixpanel.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'mixpanel/tracker'
2
+ require 'mixpanel/middleware'
2
3
 
3
4
  module Mixpanel
4
5
  end
data/mixpanel.gemspec CHANGED
@@ -2,7 +2,7 @@ files = ['README.md', 'LICENSE', 'Rakefile', 'mixpanel.gemspec', '{spec,lib}/**/
2
2
 
3
3
  spec = Gem::Specification.new do |s|
4
4
  s.name = "mixpanel"
5
- s.version = "2.2.0"
5
+ s.version = "3.0.1"
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"
@@ -17,6 +17,7 @@ spec = Gem::Specification.new do |s|
17
17
  s.add_dependency 'json'
18
18
  s.add_dependency 'rack'
19
19
  s.add_dependency 'escape'
20
+ s.add_development_dependency 'active_support'
20
21
  s.add_development_dependency 'rspec'
21
22
  s.add_development_dependency 'rack-test'
22
23
  s.add_development_dependency 'fakeweb'
@@ -1,15 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  def exec_default_appends_on(mixpanel)
4
- mixpanel.append_event("Visit", {:article => 1})
5
- mixpanel.append_event("Sign in")
6
- mixpanel.append_person_event(:first_name => "foo", :last_name => "bar", :username => "foobar")
7
- mixpanel.append_person_increment_event(:sign_in_rate)
4
+ mixpanel.append_track("Visit", {:article => 1})
5
+ mixpanel.append_track("Sign in")
6
+ mixpanel.append_set(:first_name => "foo", :last_name => "bar", :username => "foobar")
7
+ mixpanel.append_increment(:sign_in_rate)
8
8
  end
9
9
 
10
10
  def check_for_default_appends_on(txt)
11
- txt.should =~ /mixpanel\.track\("Visit",\s?\{"article":1\}\)/
12
- txt.should =~ /mixpanel\.track\("Sign in",\s?\{\}\)/
11
+ txt.should =~ /mixpanel\.track\("Visit",\s?\{.*"article":1[\{|,]/
12
+ txt.should =~ /mixpanel\.track\("Sign in",\s?\{.*"time":.*\}/
13
13
  txt.should =~ /mixpanel\.people\.set\(.*\);\nmixpanel.people.increment\(\"sign_in_rate\",\s?1\);/
14
14
  match = txt.match(/mixpanel\.people\.set\((.*\));/)
15
15
  match[1].should =~ /\"\$first_name\":\"foo\"/
@@ -18,9 +18,9 @@ def check_for_default_appends_on(txt)
18
18
  txt.should =~ /mixpanel\.people\.increment\(\"sign_in_rate\"\s?,\s?1\)/
19
19
  end
20
20
 
21
- describe Mixpanel::Tracker::Middleware do
21
+ describe Mixpanel::Middleware do
22
22
  include Rack::Test::Methods
23
-
23
+
24
24
  describe "Dummy apps, no text/html" do
25
25
  before do
26
26
  setup_rack_application(DummyApp, :body => html_document, :headers => {})
@@ -198,7 +198,7 @@ describe Mixpanel::Tracker::Middleware do
198
198
 
199
199
  describe "Tracking async appended events" do
200
200
  before do
201
- @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, {})
201
+ @mixpanel = Mixpanel::Tracker.new MIX_PANEL_TOKEN
202
202
  exec_default_appends_on @mixpanel
203
203
  end
204
204
 
@@ -266,7 +266,7 @@ describe Mixpanel::Tracker::Middleware do
266
266
 
267
267
  describe "Tracking appended events" do
268
268
  before do
269
- @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, {})
269
+ @mixpanel = Mixpanel::Tracker.new MIX_PANEL_TOKEN
270
270
  exec_default_appends_on @mixpanel
271
271
  end
272
272
 
@@ -2,20 +2,20 @@ require 'spec_helper'
2
2
 
3
3
  describe Mixpanel::Tracker do
4
4
  before(:each) do
5
- @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"})
5
+ @mixpanel = Mixpanel::Tracker.new MIX_PANEL_TOKEN, { :env => {"REMOTE_ADDR" => "127.0.0.1"} }
6
6
  end
7
7
 
8
8
  context "Initializing object" do
9
9
  it "should have an instance variable for token and events" do
10
- @mixpanel.instance_variables.map(&:to_s).should include("@token", "@env")
10
+ @mixpanel.instance_variables.map(&:to_s).should include('@token', '@async', '@persist', '@env')
11
11
  end
12
12
  end
13
13
 
14
14
  context "Cleaning appended events" do
15
15
  it "should clear the queue" do
16
- @mixpanel.append_event("Sign up")
16
+ @mixpanel.append_track("Sign up")
17
17
  @mixpanel.queue.size.should == 1
18
- @mixpanel.clear_queue
18
+ @mixpanel.queue.clear
19
19
  @mixpanel.queue.size.should == 0
20
20
  end
21
21
  end
@@ -23,60 +23,31 @@ describe Mixpanel::Tracker do
23
23
  context "Accessing Mixpanel through direct request" do
24
24
  context "Tracking events" do
25
25
  it "should track simple events" do
26
- @mixpanel.track_event("Sign up").should == true
26
+ @mixpanel.track("Sign up").should == true
27
27
  end
28
-
29
- it "should call request method with token, time value and ip address" 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(:track, params).and_return("1")
33
- @mixpanel.track_event("Sign up").should == true
34
- end
35
-
36
- it "should call request method with token, and send ip address from HTTP_X_FORWARDED_FOR" do
37
- @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, @env = {"HTTP_X_FORWARDED_FOR" => "10.1.0.2"})
38
-
39
- params = {:event => "Sign up", :properties => {:token => MIX_PANEL_TOKEN, :time => Time.now.utc.to_i, :ip => "10.1.0.2"}}
40
-
41
- @mixpanel.should_receive(:request).with(:track, params).and_return("1")
42
- @mixpanel.track_event("Sign up")
28
+
29
+ it "should track events with properties" do
30
+ @mixpanel.track('Sign up', { :likeable => true }, { :api_key => 'asdf' }).should == true
43
31
  end
44
32
  end
45
33
 
46
- context "Managing People" do
47
- it "should set person data" do
48
- @mixpanel.engage_set(DISTINCT_ID, :$email => 'test@example.com').should == true
34
+ context "Importing events" do
35
+ it "should import simple events" do
36
+ @mixpanel.import('Sign up').should == true
49
37
  end
50
38
 
51
- it "should add person data" do
52
- @mixpanel.engage_add(DISTINCT_ID, :custom => 99).should == true
39
+ it "should import events with properties" do
40
+ @mixpanel.import('Sign up', { :likeable => true }, { :api_key => 'asdf' }).should == true
53
41
  end
54
-
55
- it "should be able to call engage method directly" do
56
- @mixpanel.engage(:set, DISTINCT_ID, :$email => 'test@example.com').should == true
57
- end
58
-
59
- it "should call request method for setting" do
60
- params = {
61
- :$token => MIX_PANEL_TOKEN,
62
- :$distinct_id => DISTINCT_ID,
63
- :$set => {
64
- :$email => 'test@example.com',
65
- :custom => 'test'
66
- }}
67
- @mixpanel.should_receive(:request).with(:engage, params).and_return("1")
68
- @mixpanel.engage(:set, DISTINCT_ID, :email => 'test@example.com', :custom => 'test')
42
+ end
43
+
44
+ context "Engaging people" do
45
+ it "should set attributes" do
46
+ @mixpanel.set('person-a', { :email => 'me@domain.com', :likeable => false }).should == true
69
47
  end
70
48
 
71
- it "should call request method for adding" do
72
- params = {
73
- :$token => MIX_PANEL_TOKEN,
74
- :$distinct_id => DISTINCT_ID,
75
- :$add => {
76
- :custom => 99
77
- }}
78
- @mixpanel.should_receive(:request).with(:engage, params).and_return("1")
79
- @mixpanel.engage(:add, DISTINCT_ID, :custom => 99)
49
+ it "should increment attributes" do
50
+ @mixpanel.increment('person-a', { :tokens => 3, :money => -1 }).should == true
80
51
  end
81
52
  end
82
53
  end
@@ -84,47 +55,39 @@ describe Mixpanel::Tracker do
84
55
  context "Accessing Mixpanel through javascript API" do
85
56
  context "Appending events" do
86
57
  it "should store the event under the appropriate key" do
87
- @mixpanel.append_event("Sign up")
88
- @env.has_key?("mixpanel_events").should == true
58
+ @mixpanel.instance_variable_get(:@env).has_key?("mixpanel_events").should == true
89
59
  end
90
60
 
91
61
  it "should be the same the queue than env['mixpanel_events']" do
92
- @env['mixpanel_events'].object_id.should == @mixpanel.queue.object_id
62
+ @mixpanel.instance_variable_get(:@env)['mixpanel_events'].object_id.should == @mixpanel.queue.object_id
93
63
  end
94
64
 
95
65
  it "should append simple events" do
96
- @mixpanel.append_event("Sign up")
97
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {})
66
+ props = { :time => Time.now, :ip => 'ASDF' }
67
+ @mixpanel.append_track "Sign up", props
68
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", props)
98
69
  end
99
70
 
100
71
  it "should append events with properties" do
101
- @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
102
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
72
+ props = { :referer => 'http://example.com', :time => Time.now, :ip => 'ASDF' }
73
+ @mixpanel.append_track "Sign up", props
74
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", props)
103
75
  end
104
76
 
105
77
  it "should give direct access to queue" do
106
- @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
78
+ @mixpanel.append_track("Sign up", {:referer => 'http://example.com'})
107
79
  @mixpanel.queue.size.should == 1
108
80
  end
109
81
 
110
- it "should provide direct access to the JS api" do
111
- @mixpanel.append_api('track', "Sign up", {:referer => 'http://example.com'})
112
- mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
113
- end
114
-
115
- it "should allow identify to be called through the JS api" do
116
- @mixpanel.append_api('identify', "some@one.com")
117
- mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
118
- end
119
-
120
82
  it "should allow identify to be called through the JS api" do
121
- @mixpanel.append_api('identify', "some@one.com")
83
+ @mixpanel.append_identify "some@one.com"
122
84
  mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
123
85
  end
124
86
 
125
87
  it "should allow the tracking of super properties in JS" do
126
- @mixpanel.append_api('register', {:user_id => 12345, :email => "some@one.com"})
127
- mixpanel_queue_should_include(@mixpanel, 'register', {:user_id => 12345, :email => "some@one.com"})
88
+ props = {:user_id => 12345, :gender => 'male'}
89
+ @mixpanel.append_register props
90
+ mixpanel_queue_should_include(@mixpanel, 'register', props)
128
91
  end
129
92
  end
130
93
  end
@@ -156,24 +119,4 @@ describe Mixpanel::Tracker do
156
119
  w2.should_not == w
157
120
  end
158
121
  end
159
-
160
- context "Request modes" do
161
- it "should use the track URL" do
162
- @mixpanel.track_event("Sign up")
163
- FakeWeb.last_request.path.to_s.include?(Mixpanel::Tracker::TRACK_ENDPOINT).should == true
164
- end
165
-
166
- it "should use the engage URL" do
167
- @mixpanel.engage(:set, DISTINCT_ID, :email => 'test@example.com')
168
- FakeWeb.last_request.path.to_s.include?(Mixpanel::Tracker::ENGAGE_ENDPOINT).should == true
169
- end
170
-
171
- it "should use the import URL" do
172
- @mixpanel = Mixpanel::Tracker.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"}, { :import => true, :api_key => "ABCDEFG" })
173
- @mixpanel.track_event("Sign up")
174
- path = FakeWeb.last_request.path.to_s
175
- path.include?(Mixpanel::Tracker::IMPORT_ENDPOINT).should == true
176
- path.include?("&api_key=ABCDEFG").should == true
177
- end
178
- end
179
122
  end
data/spec/spec_helper.rb CHANGED
@@ -3,18 +3,28 @@ require File.join(File.dirname(__FILE__), "../lib", "mixpanel")
3
3
  require 'rack/test'
4
4
  require 'fakeweb'
5
5
  require 'nokogiri'
6
+ require 'active_support/core_ext/hash'
7
+
6
8
  Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
7
9
 
8
10
  MIX_PANEL_TOKEN = "e2d8b0bea559147844ffab3d607d26a6"
9
- DISTINCT_ID = "abcd1234"
11
+
10
12
 
11
13
  def mixpanel_queue_should_include(mixpanel, type, *arguments)
12
14
  mixpanel.queue.each do |event_type, event_arguments|
13
- event_arguments.should == arguments.map{|arg| arg.to_json}
14
- event_type.should == type
15
+ # hashes store keys in an undetermined order. convert to json and back and compare hash to hash, not json to json
16
+ unjsonify(event_arguments).should == json_and_back(arguments)
15
17
  end
16
18
  end
17
19
 
20
+ def json_and_back array
21
+ unjsonify array.collect { |arg| arg.to_json }
22
+ end
23
+
24
+ def unjsonify array
25
+ array.collect { |arg| JSON.parse(arg) rescue arg }
26
+ end
27
+
18
28
  # Fakeweb
19
29
  FakeWeb.allow_net_connect = false
20
30
  FakeWeb.register_uri(:any, /http:\/\/api\.mixpanel\.com.*/, :body => "1")
@@ -1,5 +1,5 @@
1
1
  def setup_rack_application(application, options = {}, mixpanel_options = {})
2
- stub!(:app).and_return(Mixpanel::Tracker::Middleware.new(application.new(options), MIX_PANEL_TOKEN, mixpanel_options))
2
+ stub!(:app).and_return(Mixpanel::Middleware.new(application.new(options), MIX_PANEL_TOKEN, mixpanel_options))
3
3
  end
4
4
 
5
5
  def html_document