thinkingdata-ruby 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,495 @@
1
+ require 'securerandom'
2
+ require 'thinkingdata-ruby/td_errors'
3
+ require 'thinkingdata-ruby/td_version'
4
+
5
+ ##
6
+ # ThinkingData module
7
+ module ThinkingData
8
+ @is_enable_log = false
9
+ @is_stringent = false
10
+
11
+ ##
12
+ # Enable SDK log or not
13
+ # @param enable [Boolean] true or false
14
+ def self.set_enable_log(enable)
15
+ unless [true, false].include? enable
16
+ enable = false
17
+ end
18
+ @is_enable_log = enable
19
+ end
20
+
21
+ ##
22
+ # Get log status
23
+ # @return [Boolean] enable or not
24
+ def self.get_enable_log
25
+ @is_enable_log
26
+ end
27
+
28
+ ##
29
+ # Check or not parameter
30
+ # @param enable [Boolean] check or not
31
+ def self.set_stringent(enable)
32
+ unless [true, false].include? enable
33
+ enable = false
34
+ end
35
+ @is_stringent = enable
36
+ end
37
+
38
+ ##
39
+ # Get parameter check status of SDK
40
+ # @return [Boolean] check or not
41
+ def self.get_stringent
42
+ @is_stringent
43
+ end
44
+
45
+ ##
46
+ # Analytics class。 Provides the function of tracking data
47
+ class TDAnalytics
48
+ LIB_PROPERTIES = {
49
+ '#lib' => 'ruby',
50
+ '#lib_version' => ThinkingData::VERSION,
51
+ }
52
+
53
+ @dynamic_block = nil
54
+
55
+ ##
56
+ # Init function
57
+ # @param consumer [consumer] data consumer: TDLoggerConsumer | TDDebugConsumer | TDBatchConsumer
58
+ # @param error_handler [TDErrorHandler] custom error handler, process SDK error. It could be nil
59
+ # @param uuid [Boolean] Whether to automatically add uuid
60
+ def initialize(consumer, error_handler = nil, uuid: false)
61
+ @error_handler = error_handler || TDErrorHandler.new
62
+ @consumer = consumer
63
+ @super_properties = {}
64
+ @uuid_enable = uuid
65
+ TDLog.info("SDK init success.")
66
+ end
67
+
68
+ ##
69
+ # Set common properties
70
+ def set_super_properties(properties, skip_local_check = false)
71
+ unless ThinkingData::get_stringent == false || skip_local_check || _check_properties(:track, properties)
72
+ @error_handler.handle(IllegalParameterError.new("Invalid super properties"))
73
+ return false
74
+ end
75
+ properties.each do |k, v|
76
+ if v.is_a?(Time)
77
+ @super_properties[k] = _format_time(v)
78
+ else
79
+ @super_properties[k] = v
80
+ end
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Clear super properties
86
+ def clear_super_properties
87
+ @super_properties = {}
88
+ end
89
+
90
+ ##
91
+ # Set dynamic super properties
92
+ def set_dynamic_super_properties(&block)
93
+ @dynamic_block = block
94
+ end
95
+
96
+ ##
97
+ # Clear dynamic super properties
98
+ def clear_dynamic_super_properties
99
+ @dynamic_block = nil
100
+ end
101
+
102
+ ##
103
+ # Report ordinary event
104
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
105
+ # distinct_id: (optional) distinct ID
106
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
107
+ # properties: (optional) string、number、Time、boolean
108
+ # time: (optional)Time
109
+ # ip: (optional) ip
110
+ # first_check_id: (optional) The value cannot be null for the first event
111
+ # skip_local_check: (optional) check data or not
112
+ def track(event_name: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil,first_check_id:nil, skip_local_check: false)
113
+ begin
114
+ _check_name event_name
115
+ _check_id(distinct_id, account_id)
116
+ unless skip_local_check
117
+ _check_properties(:track, properties)
118
+ end
119
+ rescue TDAnalyticsError => e
120
+ @error_handler.handle(e)
121
+ return false
122
+ end
123
+
124
+ _internal_track(:track, event_name: event_name, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip, first_check_id: first_check_id)
125
+ end
126
+
127
+ ##
128
+ # Report overridable event
129
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
130
+ # event_id: (require) string
131
+ # distinct_id: (optional) distinct ID
132
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
133
+ # properties: (optional) string、number、Time、boolean
134
+ # time: (optional)Time
135
+ # ip: (optional) ip
136
+ # skip_local_check: (optional) check data or not
137
+ def track_overwrite(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
138
+ begin
139
+ _check_name event_name
140
+ _check_event_id event_id
141
+ _check_id(distinct_id, account_id)
142
+ unless skip_local_check
143
+ _check_properties(:track_overwrite, properties)
144
+ end
145
+ rescue TDAnalyticsError => e
146
+ @error_handler.handle(e)
147
+ return false
148
+ end
149
+
150
+ _internal_track(:track_overwrite, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
151
+ end
152
+
153
+ ##
154
+ # Report updatable event
155
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
156
+ # event_id: (require) string
157
+ # distinct_id: (optional) distinct ID
158
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
159
+ # properties: (optional) string、number、Time、boolean
160
+ # time: (optional)Time
161
+ # ip: (optional) ip
162
+ # skip_local_check: (optional) check data or not
163
+ def track_update(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
164
+ begin
165
+ _check_name event_name
166
+ _check_event_id event_id
167
+ _check_id(distinct_id, account_id)
168
+ unless skip_local_check
169
+ _check_properties(:track_update, properties)
170
+ end
171
+ rescue TDAnalyticsError => e
172
+ @error_handler.handle(e)
173
+ return false
174
+ end
175
+
176
+ _internal_track(:track_update, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
177
+ end
178
+
179
+ ##
180
+ # Set user properties. would overwrite existing names
181
+ # distinct_id: (optional) distinct ID
182
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
183
+ # properties: (optional) string、number、Time、boolean
184
+ # ip: (optional) ip
185
+ def user_set(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
186
+ begin
187
+ _check_id(distinct_id, account_id)
188
+ _check_properties(:user_set, properties)
189
+ rescue TDAnalyticsError => e
190
+ @error_handler.handle(e)
191
+ return false
192
+ end
193
+
194
+ _internal_track(:user_set, distinct_id: distinct_id, account_id: account_id, properties: properties, ip: ip)
195
+ end
196
+
197
+ ##
198
+ # Set user properties, If such property had been set before, this message would be neglected
199
+ # distinct_id: (optional) distinct ID
200
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
201
+ # properties: (optional) string、number、Time、boolean
202
+ # ip: (optional) ip
203
+ def user_set_once(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
204
+ begin
205
+ _check_id(distinct_id, account_id)
206
+ _check_properties(:user_setOnce, properties)
207
+ rescue TDAnalyticsError => e
208
+ @error_handler.handle(e)
209
+ return false
210
+ end
211
+
212
+ _internal_track(:user_setOnce,
213
+ distinct_id: distinct_id,
214
+ account_id: account_id,
215
+ properties: properties,
216
+ ip: ip,
217
+ )
218
+ end
219
+
220
+ ##
221
+ # To append user properties of array type
222
+ # distinct_id: (optional) distinct ID
223
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
224
+ # properties: (optional) string、number、Time、boolean
225
+ def user_append(distinct_id: nil, account_id: nil, properties: {})
226
+ begin
227
+ _check_id(distinct_id, account_id)
228
+ _check_properties(:user_append, properties)
229
+ rescue TDAnalyticsError => e
230
+ @error_handler.handle(e)
231
+ return false
232
+ end
233
+
234
+ _internal_track(:user_append,
235
+ distinct_id: distinct_id,
236
+ account_id: account_id,
237
+ properties: properties,
238
+ )
239
+ end
240
+
241
+ ##
242
+ # To append user properties of array type. It filters out duplicate values
243
+ # distinct_id: (optional) distinct ID
244
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
245
+ # properties: (optional) string、number、Time、boolean
246
+ def user_uniq_append(distinct_id: nil, account_id: nil, properties: {})
247
+ begin
248
+ _check_id(distinct_id, account_id)
249
+ _check_properties(:user_uniq_append, properties)
250
+ rescue TDAnalyticsError => e
251
+ @error_handler.handle(e)
252
+ return false
253
+ end
254
+
255
+ _internal_track(:user_uniq_append,
256
+ distinct_id: distinct_id,
257
+ account_id: account_id,
258
+ properties: properties,
259
+ )
260
+ end
261
+
262
+ ##
263
+ # Clear the user properties of users
264
+ # distinct_id: (optional) distinct ID
265
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
266
+ # properties: (optional) string、number、Time、boolean
267
+ def user_unset(distinct_id: nil, account_id: nil, property: nil)
268
+ properties = {}
269
+ if property.is_a?(Array)
270
+ property.each do |k|
271
+ properties[k] = 0
272
+ end
273
+ else
274
+ properties[property] = 0
275
+ end
276
+
277
+ begin
278
+ _check_id(distinct_id, account_id)
279
+ _check_properties(:user_unset, properties)
280
+ rescue TDAnalyticsError => e
281
+ @error_handler.handle(e)
282
+ return false
283
+ end
284
+
285
+ _internal_track(:user_unset,
286
+ distinct_id: distinct_id,
287
+ account_id: account_id,
288
+ properties: properties,
289
+ )
290
+ end
291
+
292
+ ##
293
+ # To accumulate operations against the property
294
+ # distinct_id: (optional) distinct ID
295
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
296
+ # properties: (optional) string、number、Time、boolean
297
+ def user_add(distinct_id: nil, account_id: nil, properties: {})
298
+ begin
299
+ _check_id(distinct_id, account_id)
300
+ _check_properties(:user_add, properties)
301
+ rescue TDAnalyticsError => e
302
+ @error_handler.handle(e)
303
+ return false
304
+ end
305
+
306
+ _internal_track(:user_add,
307
+ distinct_id: distinct_id,
308
+ account_id: account_id,
309
+ properties: properties,
310
+ )
311
+ end
312
+
313
+ ##
314
+ # Delete a user, This operation cannot be undone
315
+ # distinct_id: (optional) distinct ID
316
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
317
+ def user_del(distinct_id: nil, account_id: nil)
318
+ begin
319
+ _check_id(distinct_id, account_id)
320
+ rescue TDAnalyticsError => e
321
+ @error_handler.handle(e)
322
+ return false
323
+ end
324
+
325
+ _internal_track(:user_del,
326
+ distinct_id: distinct_id,
327
+ account_id: account_id,
328
+ )
329
+ end
330
+
331
+ ##
332
+ # Report data immediately
333
+ def flush
334
+ TDLog.info("SDK flush data.")
335
+ return true unless defined? @consumer.flush
336
+ ret = true
337
+ begin
338
+ @consumer.flush
339
+ rescue TDAnalyticsError => e
340
+ @error_handler.handle(e)
341
+ ret = false
342
+ end
343
+ ret
344
+ end
345
+
346
+ ##
347
+ # Close and exit sdk
348
+ def close
349
+ return true unless defined? @consumer.close
350
+ ret = true
351
+ begin
352
+ @consumer.close
353
+ rescue TDAnalyticsError => e
354
+ @error_handler.handle(e)
355
+ ret = false
356
+ end
357
+
358
+ TDLog.info("SDK close.")
359
+
360
+ ret
361
+ end
362
+
363
+ private
364
+
365
+ def _internal_track(type, properties: {}, event_name: nil, event_id:nil, account_id: nil, distinct_id: nil, ip: nil,first_check_id: nil, time: nil)
366
+ if type == :track || type == :track_update || type == :track_overwrite
367
+ dynamic_properties = @dynamic_block.respond_to?(:call) ? @dynamic_block.call : {}
368
+ properties = LIB_PROPERTIES.merge(@super_properties).merge(dynamic_properties).merge(properties)
369
+ end
370
+
371
+ data = {
372
+ '#type' => type,
373
+ }
374
+
375
+ properties.each do |k, v|
376
+ if v.is_a?(Time)
377
+ properties[k] = _format_time(v)
378
+ end
379
+ end
380
+
381
+ _move_preset_properties([:'#ip', :"#time", :"#app_id", :"#uuid"], data, properties: properties)
382
+
383
+ if data[:'#time'] == nil
384
+ if time == nil
385
+ time = Time.now
386
+ end
387
+ data[:'#time'] = _format_time(time)
388
+ end
389
+
390
+ data['properties'] = properties
391
+ data['#event_name'] = event_name if (type == :track || type == :track_update || type == :track_overwrite)
392
+ data['#event_id'] = event_id if (type == :track_update || type == :track_overwrite)
393
+ data['#account_id'] = account_id if account_id
394
+ data['#distinct_id'] = distinct_id if distinct_id
395
+ data['#ip'] = ip if ip
396
+ data['#first_check_id'] = first_check_id if first_check_id
397
+ data[:'#uuid'] = SecureRandom.uuid if @uuid_enable and data[:'#uuid'] == nil
398
+
399
+ ret = true
400
+ begin
401
+ @consumer.add(data)
402
+ rescue TDAnalyticsError => e
403
+ @error_handler.handle(e)
404
+ ret = false
405
+ end
406
+
407
+ ret
408
+ end
409
+
410
+ def _format_time(time)
411
+ time.strftime("%Y-%m-%d %H:%M:%S.#{((time.to_f * 1000.0).to_i % 1000).to_s.rjust(3, "0")}")
412
+ end
413
+
414
+ def _check_event_id(event_id)
415
+ if ThinkingData::get_stringent == false
416
+ return true
417
+ end
418
+
419
+ raise IllegalParameterError.new("the event_id or property cannot be nil") if event_id.nil?
420
+ true
421
+ end
422
+
423
+ def _check_name(name)
424
+ if ThinkingData::get_stringent == false
425
+ return true
426
+ end
427
+
428
+ raise IllegalParameterError.new("the name of event or property cannot be nil") if name.nil?
429
+
430
+ unless name.instance_of?(String) || name.instance_of?(Symbol)
431
+ raise IllegalParameterError.new("#{name} is invalid. It must be String or Symbol")
432
+ end
433
+ true
434
+ end
435
+
436
+ def _check_properties(type, properties)
437
+ if ThinkingData::get_stringent == false
438
+ return true
439
+ end
440
+
441
+ unless properties.instance_of? Hash
442
+ return false
443
+ end
444
+
445
+ properties.each do |k, v|
446
+ _check_name k
447
+ next if v.nil?
448
+ unless v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(Symbol) || v.is_a?(String) || v.is_a?(Time) || !!v == v || v.is_a?(Array)
449
+ raise IllegalParameterError.new("The value of properties must be type in Integer, Float, Symbol, String, Array,and Time")
450
+ end
451
+
452
+ if type == :user_add
453
+ raise IllegalParameterError.new("Property value for user add must be numbers") unless v.is_a?(Integer) || v.is_a?(Float)
454
+ end
455
+ if v.is_a?(Array)
456
+ v.each_index do |i|
457
+ if v[i].is_a?(Time)
458
+ v[i] = _format_time(v[i])
459
+ end
460
+ end
461
+ end
462
+ end
463
+ true
464
+ end
465
+
466
+ def _check_id(distinct_id, account_id)
467
+ if ThinkingData::get_stringent == false
468
+ return true
469
+ end
470
+
471
+ raise IllegalParameterError.new("account id or distinct id must be provided.") if distinct_id.nil? && account_id.nil?
472
+ end
473
+
474
+ def _move_preset_properties(keys, data, properties: {})
475
+ property_keys = properties.keys
476
+ keys.each { |k|
477
+ if property_keys.include? k
478
+ data[k] = properties[k]
479
+ properties.delete(k)
480
+ end
481
+ }
482
+ end
483
+ end
484
+
485
+ ##
486
+ # SDK log module
487
+ class TDLog
488
+ def self.info(*msg)
489
+ if ThinkingData::get_enable_log
490
+ print("[ThinkingData][#{Time.now}] ")
491
+ puts(msg)
492
+ end
493
+ end
494
+ end
495
+ end