analytics-ruby 1.1.0 → 2.0.1
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.
- data/Gemfile.lock +47 -0
- data/analytics-ruby.gemspec +5 -7
- data/lib/segment.rb +1 -0
- data/lib/segment/analytics.rb +31 -0
- data/lib/segment/analytics/client.rb +335 -0
- data/lib/segment/analytics/defaults.rb +20 -0
- data/lib/segment/analytics/logging.rb +35 -0
- data/lib/segment/analytics/request.rb +80 -0
- data/lib/segment/analytics/response.rb +16 -0
- data/lib/segment/analytics/utils.rb +85 -0
- data/lib/segment/analytics/version.rb +6 -0
- data/lib/segment/analytics/worker.rb +59 -0
- data/spec/segment/analytics/client_spec.rb +248 -0
- data/spec/segment/analytics/worker_spec.rb +96 -0
- data/spec/segment/analytics_spec.rb +103 -0
- data/spec/spec_helper.rb +60 -55
- data/tags +135 -0
- metadata +34 -31
- data/analytics-ruby-0.6.0.gem +0 -0
- data/analytics-ruby-1.0.0.gem +0 -0
- data/lib/analytics-ruby.rb +0 -51
- data/lib/analytics-ruby/client.rb +0 -311
- data/lib/analytics-ruby/consumer.rb +0 -76
- data/lib/analytics-ruby/defaults.rb +0 -21
- data/lib/analytics-ruby/request.rb +0 -61
- data/lib/analytics-ruby/response.rb +0 -17
- data/lib/analytics-ruby/util.rb +0 -42
- data/lib/analytics-ruby/version.rb +0 -3
- data/spec/client_spec.rb +0 -212
- data/spec/consumer_spec.rb +0 -103
- data/spec/module_spec.rb +0 -136
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
analytics-ruby (2.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
predicated (0.2.6)
|
11
|
+
rake (10.3.1)
|
12
|
+
rspec (2.14.1)
|
13
|
+
rspec-core (~> 2.14.0)
|
14
|
+
rspec-expectations (~> 2.14.0)
|
15
|
+
rspec-mocks (~> 2.14.0)
|
16
|
+
rspec-core (2.14.8)
|
17
|
+
rspec-expectations (2.14.5)
|
18
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
19
|
+
rspec-mocks (2.14.6)
|
20
|
+
ruby2ruby (2.1.0)
|
21
|
+
ruby_parser (~> 3.1)
|
22
|
+
sexp_processor (~> 4.0)
|
23
|
+
ruby_parser (3.6.1)
|
24
|
+
sexp_processor (~> 4.1)
|
25
|
+
sexp_processor (4.4.3)
|
26
|
+
wrong (0.7.1)
|
27
|
+
diff-lcs (~> 1.2.5)
|
28
|
+
predicated (~> 0.2.6)
|
29
|
+
ruby2ruby (>= 2.0.1)
|
30
|
+
ruby_parser (>= 3.0.1)
|
31
|
+
sexp_processor (>= 4.0)
|
32
|
+
wrong (0.7.1-java)
|
33
|
+
diff-lcs (~> 1.2.5)
|
34
|
+
predicated (~> 0.2.6)
|
35
|
+
ruby2ruby (>= 2.0.1)
|
36
|
+
ruby_parser (>= 3.0.1)
|
37
|
+
sexp_processor (>= 4.0)
|
38
|
+
|
39
|
+
PLATFORMS
|
40
|
+
java
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
analytics-ruby!
|
45
|
+
rake (~> 10.3)
|
46
|
+
rspec (~> 2.0)
|
47
|
+
wrong (~> 0.0)
|
data/analytics-ruby.gemspec
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'analytics-ruby/version'
|
4
|
-
|
1
|
+
require File.expand_path('../lib/segment/analytics/version', __FILE__)
|
5
2
|
|
6
3
|
Gem::Specification.new do |spec|
|
7
4
|
spec.name = 'analytics-ruby'
|
8
|
-
spec.version =
|
5
|
+
spec.version = Segment::Analytics::VERSION
|
9
6
|
spec.files = Dir.glob('**/*')
|
10
7
|
spec.require_paths = ['lib']
|
11
8
|
spec.summary = 'Segment.io analytics library'
|
@@ -18,6 +15,7 @@ Gem::Specification.new do |spec|
|
|
18
15
|
# Ruby 1.8 requires json
|
19
16
|
spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9"
|
20
17
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
18
|
+
spec.add_development_dependency 'rake', '~> 10.3'
|
19
|
+
spec.add_development_dependency 'wrong', '~> 0.0'
|
20
|
+
spec.add_development_dependency 'rspec', '~> 2.0'
|
23
21
|
end
|
data/lib/segment.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'segment/analytics'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'segment/analytics/defaults'
|
2
|
+
require 'segment/analytics/utils'
|
3
|
+
require 'segment/analytics/version'
|
4
|
+
require 'segment/analytics/client'
|
5
|
+
require 'segment/analytics/worker'
|
6
|
+
require 'segment/analytics/request'
|
7
|
+
require 'segment/analytics/response'
|
8
|
+
require 'segment/analytics/logging'
|
9
|
+
|
10
|
+
module Segment
|
11
|
+
class Analytics
|
12
|
+
def initialize options = {}
|
13
|
+
Request.stub = options[:stub]
|
14
|
+
@client = Segment::Analytics::Client.new options
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing message, *args, &block
|
18
|
+
if @client.respond_to? message
|
19
|
+
@client.send message, *args, &block
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to? method_name, include_private = false
|
26
|
+
@client.respond_to?(method_missing) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
include Logging
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'time'
|
3
|
+
require 'segment/analytics/utils'
|
4
|
+
require 'segment/analytics/worker'
|
5
|
+
require 'segment/analytics/defaults'
|
6
|
+
|
7
|
+
module Segment
|
8
|
+
class Analytics
|
9
|
+
class Client
|
10
|
+
include Segment::Analytics::Utils
|
11
|
+
|
12
|
+
# public: Creates a new client
|
13
|
+
#
|
14
|
+
# options - Hash
|
15
|
+
# :write_key - String of your project's write_key
|
16
|
+
# :max_queue_size - Fixnum of the max calls to remain queued (optional)
|
17
|
+
# :on_error - Proc which handles error calls from the API
|
18
|
+
def initialize options = {}
|
19
|
+
symbolize_keys! options
|
20
|
+
|
21
|
+
@queue = Queue.new
|
22
|
+
@write_key = options[:write_key]
|
23
|
+
@max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
24
|
+
@options = options
|
25
|
+
@worker_mutex = Mutex.new
|
26
|
+
|
27
|
+
check_write_key!
|
28
|
+
|
29
|
+
at_exit { @worker_thread && @worker_thread[:should_exit] = true }
|
30
|
+
end
|
31
|
+
|
32
|
+
# public: Synchronously waits until the worker has flushed the queue.
|
33
|
+
# Use only for scripts which are not long-running, and will
|
34
|
+
# specifically exit
|
35
|
+
#
|
36
|
+
def flush
|
37
|
+
while !@queue.empty? || @worker.is_requesting?
|
38
|
+
ensure_worker_running
|
39
|
+
sleep(0.1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# public: Tracks an event
|
44
|
+
#
|
45
|
+
# options - Hash
|
46
|
+
# :event - String of event name.
|
47
|
+
# :user_id - String of the user id.
|
48
|
+
# :properties - Hash of event properties. (optional)
|
49
|
+
# :timestamp - Time of when the event occurred. (optional)
|
50
|
+
# :context - Hash of context. (optional)
|
51
|
+
def track options
|
52
|
+
symbolize_keys! options
|
53
|
+
check_user_id! options
|
54
|
+
|
55
|
+
event = options[:event]
|
56
|
+
properties = options[:properties] || {}
|
57
|
+
timestamp = options[:timestamp] || Time.new
|
58
|
+
context = options[:context] || {}
|
59
|
+
|
60
|
+
check_timestamp! timestamp
|
61
|
+
|
62
|
+
if event.nil? || event.empty?
|
63
|
+
fail ArgumentError, 'Must supply event as a non-empty string'
|
64
|
+
end
|
65
|
+
|
66
|
+
fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
|
67
|
+
isoify_dates! properties
|
68
|
+
|
69
|
+
add_context context
|
70
|
+
|
71
|
+
enqueue({
|
72
|
+
:event => event,
|
73
|
+
:userId => options[:user_id],
|
74
|
+
:anonymousId => options[:anonymous_id],
|
75
|
+
:context => context,
|
76
|
+
:integrations => options[:integrations],
|
77
|
+
:properties => properties,
|
78
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
79
|
+
:type => 'track'
|
80
|
+
})
|
81
|
+
end
|
82
|
+
|
83
|
+
# public: Identifies a user
|
84
|
+
#
|
85
|
+
# options - Hash
|
86
|
+
# :user_id - String of the user id
|
87
|
+
# :traits - Hash of user traits. (optional)
|
88
|
+
# :timestamp - Time of when the event occurred. (optional)
|
89
|
+
# :context - Hash of context. (optional)
|
90
|
+
def identify options
|
91
|
+
symbolize_keys! options
|
92
|
+
check_user_id! options
|
93
|
+
|
94
|
+
traits = options[:traits] || {}
|
95
|
+
timestamp = options[:timestamp] || Time.new
|
96
|
+
context = options[:context] || {}
|
97
|
+
|
98
|
+
check_timestamp! timestamp
|
99
|
+
|
100
|
+
fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash
|
101
|
+
isoify_dates! traits
|
102
|
+
|
103
|
+
add_context context
|
104
|
+
|
105
|
+
enqueue({
|
106
|
+
:userId => options[:user_id],
|
107
|
+
:anonymousId => options[:anonymous_id],
|
108
|
+
:integrations => options[:integrations],
|
109
|
+
:context => context,
|
110
|
+
:traits => traits,
|
111
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
112
|
+
:type => 'identify'
|
113
|
+
})
|
114
|
+
end
|
115
|
+
|
116
|
+
# public: Aliases a user from one id to another
|
117
|
+
#
|
118
|
+
# options - Hash
|
119
|
+
# :previous_id - String of the id to alias from
|
120
|
+
# :user_id - String of the id to alias to
|
121
|
+
# :timestamp - Time of when the alias occured (optional)
|
122
|
+
# :context - Hash of context (optional)
|
123
|
+
def alias(options)
|
124
|
+
symbolize_keys! options
|
125
|
+
|
126
|
+
from = options[:previous_id]
|
127
|
+
to = options[:user_id]
|
128
|
+
timestamp = options[:timestamp] || Time.new
|
129
|
+
context = options[:context] || {}
|
130
|
+
|
131
|
+
check_presence! from, 'previous_id'
|
132
|
+
check_presence! to, 'user_id'
|
133
|
+
check_timestamp! timestamp
|
134
|
+
add_context context
|
135
|
+
|
136
|
+
enqueue({
|
137
|
+
:previousId => from,
|
138
|
+
:userId => to,
|
139
|
+
:integrations => options[:integrations],
|
140
|
+
:context => context,
|
141
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
142
|
+
:type => 'alias'
|
143
|
+
})
|
144
|
+
end
|
145
|
+
|
146
|
+
# public: Associates a user identity with a group.
|
147
|
+
#
|
148
|
+
# options - Hash
|
149
|
+
# :previous_id - String of the id to alias from
|
150
|
+
# :user_id - String of the id to alias to
|
151
|
+
# :timestamp - Time of when the alias occured (optional)
|
152
|
+
# :context - Hash of context (optional)
|
153
|
+
def group(options)
|
154
|
+
symbolize_keys! options
|
155
|
+
check_user_id! options
|
156
|
+
|
157
|
+
group_id = options[:group_id]
|
158
|
+
user_id = options[:user_id]
|
159
|
+
traits = options[:traits] || {}
|
160
|
+
timestamp = options[:timestamp] || Time.new
|
161
|
+
context = options[:context] || {}
|
162
|
+
|
163
|
+
fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash
|
164
|
+
|
165
|
+
check_presence! group_id, 'group_id'
|
166
|
+
check_timestamp! timestamp
|
167
|
+
add_context context
|
168
|
+
|
169
|
+
enqueue({
|
170
|
+
:groupId => group_id,
|
171
|
+
:userId => user_id,
|
172
|
+
:traits => traits,
|
173
|
+
:integrations => options[:integrations],
|
174
|
+
:context => context,
|
175
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
176
|
+
:type => 'group'
|
177
|
+
})
|
178
|
+
end
|
179
|
+
|
180
|
+
# public: Records a page view
|
181
|
+
#
|
182
|
+
# options - Hash
|
183
|
+
# :user_id - String of the id to alias from
|
184
|
+
# :name - String name of the page
|
185
|
+
# :properties - Hash of page properties (optional)
|
186
|
+
# :timestamp - Time of when the pageview occured (optional)
|
187
|
+
# :context - Hash of context (optional)
|
188
|
+
def page(options)
|
189
|
+
symbolize_keys! options
|
190
|
+
check_user_id! options
|
191
|
+
|
192
|
+
name = options[:name].to_s
|
193
|
+
properties = options[:properties] || {}
|
194
|
+
timestamp = options[:timestamp] || Time.new
|
195
|
+
context = options[:context] || {}
|
196
|
+
|
197
|
+
fail ArgumentError, '.name must be a string' unless !name.empty?
|
198
|
+
fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
|
199
|
+
isoify_dates! properties
|
200
|
+
|
201
|
+
check_timestamp! timestamp
|
202
|
+
add_context context
|
203
|
+
|
204
|
+
enqueue({
|
205
|
+
:userId => options[:user_id],
|
206
|
+
:anonymousId => options[:anonymous_id],
|
207
|
+
:name => name,
|
208
|
+
:properties => properties,
|
209
|
+
:integrations => options[:integrations],
|
210
|
+
:context => context,
|
211
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
212
|
+
:type => 'page'
|
213
|
+
})
|
214
|
+
end
|
215
|
+
# public: Records a screen view (for a mobile app)
|
216
|
+
#
|
217
|
+
# options - Hash
|
218
|
+
# :user_id - String of the id to alias from
|
219
|
+
# :name - String name of the screen
|
220
|
+
# :properties - Hash of screen properties (optional)
|
221
|
+
# :timestamp - Time of when the screen occured (optional)
|
222
|
+
# :context - Hash of context (optional)
|
223
|
+
def screen(options)
|
224
|
+
symbolize_keys! options
|
225
|
+
check_user_id! options
|
226
|
+
|
227
|
+
name = options[:name].to_s
|
228
|
+
properties = options[:properties] || {}
|
229
|
+
timestamp = options[:timestamp] || Time.new
|
230
|
+
context = options[:context] || {}
|
231
|
+
|
232
|
+
fail ArgumentError, '.name must be a string' if name.empty?
|
233
|
+
fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash
|
234
|
+
isoify_dates! properties
|
235
|
+
|
236
|
+
check_timestamp! timestamp
|
237
|
+
add_context context
|
238
|
+
|
239
|
+
enqueue({
|
240
|
+
:userId => options[:user_id],
|
241
|
+
:anonymousId => options[:anonymous_id],
|
242
|
+
:name => name,
|
243
|
+
:properties => properties,
|
244
|
+
:integrations => options[:integrations],
|
245
|
+
:context => context,
|
246
|
+
:timestamp => timestamp.iso8601,
|
247
|
+
:type => 'screen'
|
248
|
+
})
|
249
|
+
end
|
250
|
+
|
251
|
+
# public: Returns the number of queued messages
|
252
|
+
#
|
253
|
+
# returns Fixnum of messages in the queue
|
254
|
+
def queued_messages
|
255
|
+
@queue.length
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
# private: Enqueues the action.
|
261
|
+
#
|
262
|
+
# returns Boolean of whether the item was added to the queue.
|
263
|
+
def enqueue(action)
|
264
|
+
# add our request id for tracing purposes
|
265
|
+
action[:messageId] = uid
|
266
|
+
unless queue_full = @queue.length >= @max_queue_size
|
267
|
+
ensure_worker_running
|
268
|
+
@queue << action
|
269
|
+
end
|
270
|
+
!queue_full
|
271
|
+
end
|
272
|
+
|
273
|
+
# private: Ensures that a string is non-empty
|
274
|
+
#
|
275
|
+
# obj - String|Number that must be non-blank
|
276
|
+
# name - Name of the validated value
|
277
|
+
#
|
278
|
+
def check_presence!(obj, name)
|
279
|
+
if obj.nil? || (obj.is_a?(String) && obj.empty?)
|
280
|
+
fail ArgumentError, "#{name} must be given"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# private: Adds contextual information to the call
|
285
|
+
#
|
286
|
+
# context - Hash of call context
|
287
|
+
def add_context(context)
|
288
|
+
context[:library] = { :name => "analytics-ruby", :version => Segment::Analytics::VERSION.to_s }
|
289
|
+
end
|
290
|
+
|
291
|
+
# private: Checks that the write_key is properly initialized
|
292
|
+
def check_write_key!
|
293
|
+
fail ArgumentError, 'Write key must be initialized' if @write_key.nil?
|
294
|
+
end
|
295
|
+
|
296
|
+
# private: Checks the timstamp option to make sure it is a Time.
|
297
|
+
def check_timestamp!(timestamp)
|
298
|
+
fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
|
299
|
+
end
|
300
|
+
|
301
|
+
def event attrs
|
302
|
+
symbolize_keys! attrs
|
303
|
+
|
304
|
+
{
|
305
|
+
:userId => user_id,
|
306
|
+
:name => name,
|
307
|
+
:properties => properties,
|
308
|
+
:context => context,
|
309
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
310
|
+
:type => 'screen'
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
def check_user_id! options
|
315
|
+
fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id]
|
316
|
+
end
|
317
|
+
|
318
|
+
def ensure_worker_running
|
319
|
+
return if worker_running?
|
320
|
+
@worker_mutex.synchronize do
|
321
|
+
return if worker_running?
|
322
|
+
@worker_thread = Thread.new do
|
323
|
+
@worker = Worker.new @queue, @write_key, @options
|
324
|
+
@worker.run
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def worker_running?
|
330
|
+
@worker_thread && @worker_thread.alive?
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Segment
|
2
|
+
class Analytics
|
3
|
+
module Defaults
|
4
|
+
module Request
|
5
|
+
HOST = 'api.segment.io'
|
6
|
+
PORT = 443
|
7
|
+
PATH = '/v1/import'
|
8
|
+
SSL = true
|
9
|
+
HEADERS = { :accept => 'application/json' }
|
10
|
+
RETRIES = 4
|
11
|
+
BACKOFF = 30.0
|
12
|
+
end
|
13
|
+
|
14
|
+
module Queue
|
15
|
+
BATCH_SIZE = 100
|
16
|
+
MAX_SIZE = 10000
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|