greenfinch-ruby 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.idea/.gitignore +8 -0
- data/.idea/greenfinch-ruby.iml +16 -0
- data/.idea/inspectionProfiles/Project_Default.xml +6 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +2 -0
- data/LICENSE +190 -0
- data/README.md +124 -0
- data/Rakefile +13 -0
- data/Readme.rdoc +109 -0
- data/demo/faraday_consumer.rb +40 -0
- data/demo/out_of_process_consumer.rb +71 -0
- data/demo/simple_messages.rb +9 -0
- data/greenfinch-ruby.gemspec +21 -0
- data/lib/greenfinch-ruby.rb +3 -0
- data/lib/greenfinch-ruby/consumer.rb +271 -0
- data/lib/greenfinch-ruby/error.rb +46 -0
- data/lib/greenfinch-ruby/events.rb +140 -0
- data/lib/greenfinch-ruby/groups.rb +201 -0
- data/lib/greenfinch-ruby/people.rb +254 -0
- data/lib/greenfinch-ruby/tracker.rb +184 -0
- data/lib/greenfinch-ruby/version.rb +3 -0
- data/spec/mixpanel-ruby/consumer_spec.rb +208 -0
- data/spec/mixpanel-ruby/error_spec.rb +76 -0
- data/spec/mixpanel-ruby/events_spec.rb +76 -0
- data/spec/mixpanel-ruby/groups_spec.rb +159 -0
- data/spec/mixpanel-ruby/people_spec.rb +220 -0
- data/spec/mixpanel-ruby/tracker_spec.rb +134 -0
- data/spec/spec_helper.rb +15 -0
- metadata +132 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'greenfinch-ruby/events.rb'
|
2
|
+
require 'greenfinch-ruby/people.rb'
|
3
|
+
require 'greenfinch-ruby/groups.rb'
|
4
|
+
|
5
|
+
module Greenfinch
|
6
|
+
# Use Greenfinch::Tracker to track events and profile updates in your application.
|
7
|
+
# To track an event, call
|
8
|
+
#
|
9
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
10
|
+
# Greenfinch::Tracker.track(a_distinct_id, an_event_name, {properties})
|
11
|
+
#
|
12
|
+
# To send people updates, call
|
13
|
+
#
|
14
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
15
|
+
# tracker.people.set(a_distinct_id, {properties})
|
16
|
+
#
|
17
|
+
# To send groups updates, call
|
18
|
+
#
|
19
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
20
|
+
# tracker.groups.set(group_key, group_id, {properties})
|
21
|
+
#
|
22
|
+
# You can find your project token in the settings dialog for your
|
23
|
+
# project, inside of the Greenfinch web application.
|
24
|
+
#
|
25
|
+
# Greenfinch::Tracker is a subclass of Greenfinch::Events, and exposes
|
26
|
+
# an instance of Greenfinch::People as Tracker#people
|
27
|
+
# and an instance of Greenfinch::Groups as Tracker#groups
|
28
|
+
class Tracker < Events
|
29
|
+
# An instance of Greenfinch::People. Use this to
|
30
|
+
# send profile updates
|
31
|
+
attr_reader :people
|
32
|
+
|
33
|
+
# An instance of Greenfinch::Groups. Use this to send groups updates
|
34
|
+
attr_reader :groups
|
35
|
+
|
36
|
+
# Takes your Greenfinch project token, as a string.
|
37
|
+
#
|
38
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
39
|
+
#
|
40
|
+
# By default, the tracker will send an message to Greenfinch
|
41
|
+
# synchronously with each call, using an instance of Greenfinch::Consumer.
|
42
|
+
#
|
43
|
+
# You can also provide a block to the constructor
|
44
|
+
# to specify particular consumer behaviors (for
|
45
|
+
# example, if you wanted to write your messages to
|
46
|
+
# a queue instead of sending them directly to Greenfinch)
|
47
|
+
#
|
48
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN) do |type, message|
|
49
|
+
# @kestrel.set(MY_GREENFINCH_QUEUE, [type,message].to_json)
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# If a block is provided, it is passed a type (one of :event or :profile_update)
|
53
|
+
# and a string message. This same format is accepted by Greenfinch::Consumer#send!
|
54
|
+
# and Greenfinch::BufferedConsumer#send!
|
55
|
+
def initialize(token, service_name, debug, error_handler=nil, &block)
|
56
|
+
super(token, service_name, debug, error_handler, &block)
|
57
|
+
@token = token
|
58
|
+
@service_name = service_name
|
59
|
+
@debug = debug
|
60
|
+
end
|
61
|
+
|
62
|
+
# A call to #track is a report that an event has occurred. #track
|
63
|
+
# takes a distinct_id representing the source of that event (for
|
64
|
+
# example, a user id), an event name describing the event, and a
|
65
|
+
# set of properties describing that event. Properties are provided
|
66
|
+
# as a Hash with string keys and strings, numbers or booleans as
|
67
|
+
# values.
|
68
|
+
#
|
69
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
70
|
+
#
|
71
|
+
# # Track that user "12345"'s credit card was declined
|
72
|
+
# tracker.track("12345", "Credit Card Declined")
|
73
|
+
#
|
74
|
+
# # Properties describe the circumstances of the event,
|
75
|
+
# # or aspects of the source or user associated with the event
|
76
|
+
# tracker.track("12345", "Welcome Email Sent", {
|
77
|
+
# 'Email Template' => 'Pretty Pink Welcome',
|
78
|
+
# 'User Sign-up Cohort' => 'July 2013'
|
79
|
+
# })
|
80
|
+
def track(distinct_id, event, properties={}, ip=nil)
|
81
|
+
# This is here strictly to allow rdoc to include the relevant
|
82
|
+
# documentation
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
# A call to #import is to import an event occurred in the past. #import
|
87
|
+
# takes a distinct_id representing the source of that event (for
|
88
|
+
# example, a user id), an event name describing the event, and a
|
89
|
+
# set of properties describing that event. Properties are provided
|
90
|
+
# as a Hash with string keys and strings, numbers or booleans as
|
91
|
+
# values.
|
92
|
+
#
|
93
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
94
|
+
#
|
95
|
+
# # Import event that user "12345"'s credit card was declined
|
96
|
+
# tracker.import("API_KEY", "12345", "Credit Card Declined", {
|
97
|
+
# 'time' => 1310111365
|
98
|
+
# })
|
99
|
+
#
|
100
|
+
# # Properties describe the circumstances of the event,
|
101
|
+
# # or aspects of the source or user associated with the event
|
102
|
+
# tracker.import("API_KEY", "12345", "Welcome Email Sent", {
|
103
|
+
# 'Email Template' => 'Pretty Pink Welcome',
|
104
|
+
# 'User Sign-up Cohort' => 'July 2013',
|
105
|
+
# 'time' => 1310111365
|
106
|
+
# })
|
107
|
+
# def import(api_key, distinct_id, event, properties={}, ip=nil)
|
108
|
+
# # This is here strictly to allow rdoc to include the relevant
|
109
|
+
# # documentation
|
110
|
+
# super
|
111
|
+
# end
|
112
|
+
|
113
|
+
# Creates a distinct_id alias. \Events and updates with an alias
|
114
|
+
# will be considered by greenfinch to have the same source, and
|
115
|
+
# refer to the same profile.
|
116
|
+
#
|
117
|
+
# Multiple aliases can map to the same real_id, once a real_id is
|
118
|
+
# used to track events or send updates, it should never be used as
|
119
|
+
# an alias itself.
|
120
|
+
#
|
121
|
+
# Alias requests are always sent synchronously, directly to
|
122
|
+
# the \Greenfinch service, regardless of how the tracker is configured.
|
123
|
+
# def alias(alias_id, real_id, events_endpoint=nil)
|
124
|
+
# consumer = Greenfinch::Consumer.new(events_endpoint)
|
125
|
+
# data = {
|
126
|
+
# 'event' => '$create_alias',
|
127
|
+
# 'properties' => {
|
128
|
+
# 'distinct_id' => real_id,
|
129
|
+
# 'alias' => alias_id,
|
130
|
+
# 'token' => @token,
|
131
|
+
# }
|
132
|
+
# }
|
133
|
+
#
|
134
|
+
# message = {'data' => data}
|
135
|
+
#
|
136
|
+
# ret = true
|
137
|
+
# begin
|
138
|
+
# consumer.send!(:event, message.to_json)
|
139
|
+
# rescue GreenfinchError => e
|
140
|
+
# @error_handler.handle(e)
|
141
|
+
# ret = false
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# ret
|
145
|
+
# end
|
146
|
+
|
147
|
+
# A call to #generate_tracking_url will return a formatted url for
|
148
|
+
# pixel based tracking. #generate_tracking_url takes a distinct_id
|
149
|
+
# representing the source of that event (for example, a user id),
|
150
|
+
# an event name describing the event, and a set of properties describing
|
151
|
+
# that event. Properties are provided as a Hash with string keys and
|
152
|
+
# strings, numbers or booleans as values. For more information, please see:
|
153
|
+
# https://greenfinch.com/docs/api-documentation/pixel-based-event-tracking
|
154
|
+
#
|
155
|
+
# tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN)
|
156
|
+
#
|
157
|
+
# # generate pixel tracking url in order to track that user
|
158
|
+
# # "12345"'s credit card was declined
|
159
|
+
# url = tracker.generate_tracking_url("12345", "Credit Card Declined", {
|
160
|
+
# 'time' => 1310111365
|
161
|
+
# })
|
162
|
+
#
|
163
|
+
# url == 'https://api.greenfinch.com/track/?data=[BASE_64_JSON_EVENT]&ip=1&img=1'
|
164
|
+
# def generate_tracking_url(distinct_id, event, properties={}, endpoint=nil)
|
165
|
+
# properties = {
|
166
|
+
# 'distinct_id' => distinct_id,
|
167
|
+
# 'token' => @token,
|
168
|
+
# 'time' => Time.now.to_i,
|
169
|
+
# 'mp_lib' => 'ruby',
|
170
|
+
# '$lib_version' => Greenfinch::VERSION,
|
171
|
+
# }.merge(properties)
|
172
|
+
#
|
173
|
+
# raw_data = {
|
174
|
+
# 'event' => event,
|
175
|
+
# 'properties' => properties,
|
176
|
+
# }
|
177
|
+
#
|
178
|
+
# endpoint = endpoint || 'https://api.greenfinch.com/track/'
|
179
|
+
# data = Base64.urlsafe_encode64(raw_data.to_json)
|
180
|
+
#
|
181
|
+
# "#{endpoint}?data=#{data}&ip=1&img=1"
|
182
|
+
# end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'webmock'
|
4
|
+
|
5
|
+
require 'mixpanel-ruby/consumer'
|
6
|
+
|
7
|
+
describe Mixpanel::Consumer do
|
8
|
+
before { WebMock.reset! }
|
9
|
+
|
10
|
+
shared_examples_for 'consumer' do
|
11
|
+
it 'should send a request to api.mixpanel.com/track on events' do
|
12
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
13
|
+
subject.send!(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json)
|
14
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
15
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should send a request to api.mixpanel.com/people on profile updates' do
|
19
|
+
stub_request(:any, 'https://api.mixpanel.com/engage').to_return({:body => '{"status": 1, "error": null}'})
|
20
|
+
subject.send!(:profile_update, {'data' => 'TEST EVENT MESSAGE'}.to_json)
|
21
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/engage').
|
22
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should send a request to api.mixpanel.com/groups on groups updates' do
|
26
|
+
stub_request(:any, 'https://api.mixpanel.com/groups').to_return({:body => '{"status": 1, "error": null}'})
|
27
|
+
subject.send!(:group_update, {'data' => 'TEST EVENT MESSAGE'}.to_json)
|
28
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/groups').
|
29
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should send a request to api.mixpanel.com/import on event imports' do
|
33
|
+
stub_request(:any, 'https://api.mixpanel.com/import').to_return({:body => '{"status": 1, "error": null}'})
|
34
|
+
subject.send!(:import, {'data' => 'TEST EVENT MESSAGE', 'api_key' => 'API_KEY','verbose' => '1' }.to_json)
|
35
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/import').
|
36
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'api_key' => 'API_KEY', 'verbose' => '1' })
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should encode long messages without newlines' do
|
40
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
41
|
+
subject.send!(:event, {'data' => 'BASE64-ENCODED VERSION OF BIN. THIS METHOD COMPLIES WITH RFC 2045. LINE FEEDS ARE ADDED TO EVERY 60 ENCODED CHARACTORS. IN RUBY 1.8 WE NEED TO JUST CALL ENCODE64 AND REMOVE THE LINE FEEDS, IN RUBY 1.9 WE CALL STRIC_ENCODED64 METHOD INSTEAD'}.to_json)
|
42
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
43
|
+
with(:body => {'data' => 'IkJBU0U2NC1FTkNPREVEIFZFUlNJT04gT0YgQklOLiBUSElTIE1FVEhPRCBDT01QTElFUyBXSVRIIFJGQyAyMDQ1LiBMSU5FIEZFRURTIEFSRSBBRERFRCBUTyBFVkVSWSA2MCBFTkNPREVEIENIQVJBQ1RPUlMuIElOIFJVQlkgMS44IFdFIE5FRUQgVE8gSlVTVCBDQUxMIEVOQ09ERTY0IEFORCBSRU1PVkUgVEhFIExJTkUgRkVFRFMsIElOIFJVQlkgMS45IFdFIENBTEwgU1RSSUNfRU5DT0RFRDY0IE1FVEhPRCBJTlNURUFEIg==', 'verbose' => '1'})
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should provide thorough information in case mixpanel fails' do
|
47
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:status => 401, :body => "nutcakes"})
|
48
|
+
expect { subject.send!(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json) }.to raise_exception('Could not write to Mixpanel, server responded with 401 returning: \'nutcakes\'')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should still respond to send' do
|
52
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
53
|
+
subject.send(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json)
|
54
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
55
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should raise server error if response body is empty' do
|
59
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => ''})
|
60
|
+
expect { subject.send!(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json) }.to raise_exception(Mixpanel::ServerError, /Could not interpret Mixpanel server response: ''/)
|
61
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
62
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should raise server error when verbose is disabled', :skip => true do
|
66
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '0'})
|
67
|
+
expect { subject.send!(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json) }.to raise_exception(Mixpanel::ServerError, /Could not interpret Mixpanel server response: '0'/)
|
68
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
69
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'raw consumer' do
|
74
|
+
it_behaves_like 'consumer'
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'custom request consumer' do
|
78
|
+
subject do
|
79
|
+
ret = Mixpanel::Consumer.new
|
80
|
+
class << ret
|
81
|
+
attr_reader :called
|
82
|
+
def request(*args)
|
83
|
+
@called = true
|
84
|
+
super(*args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
ret
|
89
|
+
end
|
90
|
+
|
91
|
+
after(:each) do
|
92
|
+
expect(subject.called).to be_truthy
|
93
|
+
end
|
94
|
+
|
95
|
+
it_behaves_like 'consumer'
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe Mixpanel::BufferedConsumer do
|
101
|
+
let(:max_length) { 10 }
|
102
|
+
before { WebMock.reset! }
|
103
|
+
|
104
|
+
context 'Default BufferedConsumer' do
|
105
|
+
subject { Mixpanel::BufferedConsumer.new(nil, nil, nil, max_length) }
|
106
|
+
|
107
|
+
it 'should not send a request for a single message until flush is called' do
|
108
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
109
|
+
subject.send!(:event, {'data' => 'TEST EVENT 1'}.to_json)
|
110
|
+
expect(WebMock).to have_not_requested(:post, 'https://api.mixpanel.com/track')
|
111
|
+
|
112
|
+
subject.flush()
|
113
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
114
|
+
with(:body => {'data' => 'WyJURVNUIEVWRU5UIDEiXQ==', 'verbose' => '1' })
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should still respond to send' do
|
118
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
119
|
+
subject.send(:event, {'data' => 'TEST EVENT 1'}.to_json)
|
120
|
+
expect(WebMock).to have_not_requested(:post, 'https://api.mixpanel.com/track')
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should send one message when max_length events are tracked' do
|
124
|
+
stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
|
125
|
+
|
126
|
+
max_length.times do |i|
|
127
|
+
subject.send!(:event, {'data' => "x #{i}"}.to_json)
|
128
|
+
end
|
129
|
+
|
130
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/track').
|
131
|
+
with(:body => {'data' => 'WyJ4IDAiLCJ4IDEiLCJ4IDIiLCJ4IDMiLCJ4IDQiLCJ4IDUiLCJ4IDYiLCJ4IDciLCJ4IDgiLCJ4IDkiXQ==', 'verbose' => '1' })
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should send one message per api key on import' do
|
135
|
+
stub_request(:any, 'https://api.mixpanel.com/import').to_return({:body => '{"status": 1, "error": null}'})
|
136
|
+
subject.send!(:import, {'data' => 'TEST EVENT 1', 'api_key' => 'KEY 1'}.to_json)
|
137
|
+
subject.send!(:import, {'data' => 'TEST EVENT 1', 'api_key' => 'KEY 2'}.to_json)
|
138
|
+
subject.send!(:import, {'data' => 'TEST EVENT 2', 'api_key' => 'KEY 1'}.to_json)
|
139
|
+
subject.send!(:import, {'data' => 'TEST EVENT 2', 'api_key' => 'KEY 2'}.to_json)
|
140
|
+
subject.flush
|
141
|
+
|
142
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/import').
|
143
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgMSI=', 'api_key' => 'KEY 1', 'verbose' => '1' })
|
144
|
+
|
145
|
+
expect(WebMock).to have_requested(:post, 'https://api.mixpanel.com/import').
|
146
|
+
with(:body => {'data' => 'IlRFU1QgRVZFTlQgMSI=', 'api_key' => 'KEY 2', 'verbose' => '1' })
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'BufferedConsumer with block' do
|
151
|
+
let(:messages_seen) { [] }
|
152
|
+
subject do
|
153
|
+
Mixpanel::BufferedConsumer.new(nil, nil, nil, 3) do |type, message|
|
154
|
+
messages_seen << [type, message]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should call block instead of making default requests on flush' do
|
159
|
+
3.times do |i|
|
160
|
+
subject.send!(:event, {'data' => "x #{i}"}.to_json)
|
161
|
+
end
|
162
|
+
|
163
|
+
expect(messages_seen).to match_array(
|
164
|
+
[[:event, "{\"data\":[\"x 0\",\"x 1\",\"x 2\"]}"]]
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with failing requests' do
|
171
|
+
let(:sent_messages) { [] }
|
172
|
+
let(:submission_queue) { [] }
|
173
|
+
subject do
|
174
|
+
Mixpanel::BufferedConsumer.new(nil, nil, nil, 2) do |type, message|
|
175
|
+
raise Mixpanel::ServerError if submission_queue.shift == :fail
|
176
|
+
sent_messages << [type, message]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'clears any slices that complete on flush' do
|
181
|
+
# construct a consumer that is backed up and has a multi-slice buffer
|
182
|
+
3.times { submission_queue << :fail }
|
183
|
+
4.times do |i|
|
184
|
+
begin
|
185
|
+
subject.send!(:event, {'data' => i}.to_json)
|
186
|
+
rescue Mixpanel::ServerError
|
187
|
+
end
|
188
|
+
end
|
189
|
+
expect(sent_messages).to match_array([])
|
190
|
+
|
191
|
+
submission_queue << :pass
|
192
|
+
submission_queue << :fail
|
193
|
+
|
194
|
+
expect { subject.flush }.to raise_error Mixpanel::ServerError
|
195
|
+
expect(sent_messages).to match_array([
|
196
|
+
[:event, '{"data":[0,1]}']
|
197
|
+
])
|
198
|
+
|
199
|
+
submission_queue << :pass
|
200
|
+
subject.flush
|
201
|
+
expect(sent_messages).to match_array([
|
202
|
+
[:event, '{"data":[0,1]}'],
|
203
|
+
[:event, '{"data":[2,3]}'],
|
204
|
+
])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'mixpanel-ruby/error.rb'
|
4
|
+
require 'mixpanel-ruby/events.rb'
|
5
|
+
|
6
|
+
class TestErrorHandler < Mixpanel::ErrorHandler
|
7
|
+
def initialize(log)
|
8
|
+
@log = log
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(error)
|
12
|
+
@log << error.to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Mixpanel::ErrorHandler do
|
17
|
+
it "should respond to #handle`" do
|
18
|
+
error_handler = Mixpanel::ErrorHandler.new
|
19
|
+
expect(error_handler.respond_to?(:handle)).to be true
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'without a customer error_handler' do
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
@tracker = Mixpanel::Tracker.new('TEST TOKEN') do |type, message|
|
26
|
+
raise Mixpanel::MixpanelError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should silence errors in track calls" do
|
31
|
+
expect {
|
32
|
+
expect(@tracker.track('TEST ID', 'Test Event')).to be false
|
33
|
+
}.to_not raise_error
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should handle errors in import calls" do
|
37
|
+
expect {
|
38
|
+
expect(@tracker.import('TEST API KEY', 'TEST DISTINCT_ID', 'Test Event')).to be false
|
39
|
+
}.to_not raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should handle errors in people calls" do
|
43
|
+
expect {
|
44
|
+
expect(@tracker.people.set('TEST ID', {})).to be false
|
45
|
+
}.to_not raise_error
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a custom error_handler' do
|
51
|
+
|
52
|
+
before(:each) do
|
53
|
+
@log = []
|
54
|
+
@error_handler = TestErrorHandler.new(@log)
|
55
|
+
@tracker = Mixpanel::Tracker.new('TEST TOKEN', @error_handler) do |type, message|
|
56
|
+
raise Mixpanel::MixpanelError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should handle errors in track calls" do
|
61
|
+
@tracker.track('TEST ID', 'Test Event', {})
|
62
|
+
expect(@log).to eq(['Mixpanel::MixpanelError'])
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should handle errors in import calls" do
|
66
|
+
@tracker.import('TEST API KEY', 'TEST DISTINCT_ID', 'Test Event')
|
67
|
+
expect(@log).to eq(['Mixpanel::MixpanelError'])
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should handle errors in people calls" do
|
71
|
+
@tracker.people.set('TEST ID', {})
|
72
|
+
expect(@log).to eq(['Mixpanel::MixpanelError'])
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|