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 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)
@@ -1,11 +1,8 @@
1
- $:.push File.expand_path('../lib', __FILE__)
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 = AnalyticsRuby::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('rake')
22
- spec.add_development_dependency('rspec')
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