thinkingdata-ruby 1.2.0 → 2.0.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.
@@ -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