vwo-sdk 1.5.0 → 1.15.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.
@@ -1,4 +1,4 @@
1
- # Copyright 2019-2020 Wingify Software Pvt. Ltd.
1
+ # Copyright 2019-2021 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -24,6 +24,20 @@ class VWO
24
24
  uri.query = URI.encode_www_form(params)
25
25
  Net::HTTP.get_response(uri)
26
26
  end
27
+
28
+ def self.post(url, params, post_data)
29
+ uri = URI.parse(url)
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ http.use_ssl = true
32
+ uri.query = URI.encode_www_form(params)
33
+ headers = {
34
+ 'Authorization'=>params[:env],
35
+ 'Content-Type' =>'application/json',
36
+ 'Accept'=>'application/json'
37
+ }
38
+ response = http.post(uri, post_data.to_json, headers)
39
+ response
40
+ end
27
41
  end
28
42
  end
29
43
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2019-2020 Wingify Software Pvt. Ltd.
1
+ # Copyright 2019-2021 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2019-2020 Wingify Software Pvt. Ltd.
1
+ # Copyright 2019-2021 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2019-2020 Wingify Software Pvt. Ltd.
1
+ # Copyright 2019-2021 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
15
15
  require 'json'
16
16
  require 'json-schema'
17
17
  require_relative '../schemas/settings_file'
18
+ require_relative '../logger'
19
+ require_relative '../enums'
20
+ require_relative '../constants'
18
21
 
19
22
  class VWO
20
23
  module Utils
@@ -59,6 +62,87 @@ class VWO
59
62
  def valid_basic_data_type?(val)
60
63
  valid_number?(val) || valid_string?(val) || valid_boolean?(val)
61
64
  end
65
+
66
+ # Validates if the value passed batch_events has correct data type and values or not.
67
+ #
68
+ # Args: batch_events [Hash]: value to be tested
69
+ #
70
+ # @return: [Boolean]: True if all conditions are passed else False
71
+ def is_valid_batch_event_settings(batch_events)
72
+ logger = VWO::Logger.get_instance
73
+ events_per_request = batch_events[:events_per_request]
74
+ request_time_interval = batch_events[:request_time_interval]
75
+
76
+ unless events_per_request || request_time_interval
77
+ logger.log(
78
+ VWO::LogLevelEnum::ERROR,
79
+ format(
80
+ VWO::LogMessageEnum::ErrorMessages::EVENT_BATCHING_INSUFFICIENT,
81
+ file: VWO::FileNameEnum::ValidateUtil
82
+ )
83
+ )
84
+ return false
85
+ end
86
+
87
+ if (request_time_interval && !valid_number?(request_time_interval))
88
+ logger.log(
89
+ VWO::LogLevelEnum::ERROR,
90
+ format(
91
+ VWO::LogMessageEnum::ErrorMessages::REQUEST_TIME_INTERVAL_INVALID,
92
+ file: VWO::FileNameEnum::ValidateUtil
93
+ )
94
+ )
95
+ return false
96
+ end
97
+
98
+ if (events_per_request && !valid_number?(events_per_request))
99
+ logger.log(
100
+ VWO::LogLevelEnum::ERROR,
101
+ format(
102
+ VWO::LogMessageEnum::ErrorMessages::EVENTS_PER_REQUEST_INVALID,
103
+ file: VWO::FileNameEnum::ValidateUtil
104
+ )
105
+ )
106
+ return false
107
+ end
108
+
109
+ if events_per_request && (events_per_request < VWO::MIN_EVENTS_PER_REQUEST || events_per_request > VWO::MAX_EVENTS_PER_REQUEST)
110
+ logger.log(
111
+ VWO::LogLevelEnum::ERROR,
112
+ format(
113
+ VWO::LogMessageEnum::ErrorMessages::EVENTS_PER_REQUEST_OUT_OF_BOUNDS,
114
+ file: VWO::FileNameEnum::ValidateUtil,
115
+ min_value: VWO::MIN_EVENTS_PER_REQUEST,
116
+ max_value: VWO::MAX_EVENTS_PER_REQUEST
117
+ )
118
+ )
119
+ return false
120
+ end
121
+
122
+ if request_time_interval && request_time_interval < VWO::MIN_REQUEST_TIME_INTERVAL
123
+ logger.log(
124
+ VWO::LogLevelEnum::ERROR,
125
+ format(
126
+ VWO::LogMessageEnum::ErrorMessages::REQUEST_TIME_INTERVAL_OUT_OF_BOUNDS,
127
+ file: VWO::FileNameEnum::ValidateUtil,
128
+ min_value: VWO::MIN_REQUEST_TIME_INTERVAL
129
+ )
130
+ )
131
+ return false
132
+ end
133
+
134
+ if batch_events.key?(:flushCallback) && !batch_events[:flushCallback].is_a?(Method)
135
+ logger.log(
136
+ VWO::LogLevelEnum::ERROR,
137
+ format(
138
+ VWO::LogMessageEnum::ErrorMessages::FLUSH_CALLBACK_INVALID,
139
+ file: VWO::FileNameEnum::ValidateUtil
140
+ )
141
+ )
142
+ return false
143
+ end
144
+ true
145
+ end
62
146
  end
63
147
  end
64
148
  end
data/lib/vwo.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2019-2020 Wingify Software Pvt. Ltd.
1
+ # Copyright 2019-2021 Wingify Software Pvt. Ltd.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -25,11 +25,14 @@ require_relative 'vwo/utils/feature'
25
25
  require_relative 'vwo/utils/custom_dimensions'
26
26
  require_relative 'vwo/constants'
27
27
  require_relative 'vwo/core/variation_decider'
28
+ require_relative 'vwo/services/batch_events_dispatcher'
29
+ require_relative 'vwo/services/batch_events_queue'
30
+ require_relative 'vwo/services/usage_stats'
28
31
 
29
32
  # VWO main file
30
33
  class VWO
31
- attr_accessor :is_instance_valid, :logger
32
-
34
+ attr_accessor :is_instance_valid, :logger, :settings_file_manager, :variation_decider
35
+ attr_reader :usage_stats
33
36
  include Enums
34
37
  include Utils::Validations
35
38
  include Utils::Feature
@@ -65,7 +68,15 @@ class VWO
65
68
  @is_development_mode = is_development_mode
66
69
  @logger = VWO::Logger.get_instance(logger)
67
70
  @logger.instance.level = options[:log_level] if (0..5).include?(options[:log_level])
71
+ usage_stats = {}
72
+
73
+ usage_stats[:cl] = 1 if logger
74
+ usage_stats[:ll] = 1 if options[:log_level]
75
+ usage_stats[:ss] = 1 if @user_storage
76
+ usage_stats[:ig] = 1 if options.key?(:integrations)
77
+ usage_stats[:eb] = 1 if options.key?(:batch_events)
68
78
 
79
+ @settings_file_manager = VWO::Services::SettingsFileManager.new(@account_id, @sdk_key)
69
80
  unless valid_settings_file?(get_settings(settings_file))
70
81
  @logger.log(
71
82
  LogLevelEnum::ERROR,
@@ -74,6 +85,45 @@ class VWO
74
85
  @is_instance_valid = false
75
86
  return
76
87
  end
88
+
89
+ if options.key?(:should_track_returning_user)
90
+ if [true, false].include? options[:should_track_returning_user]
91
+ @should_track_returning_user = options[:should_track_returning_user]
92
+ usage_stats[:tr] = 1 if @should_track_returning_user
93
+ else
94
+ @logger.log(
95
+ LogLevelEnum::ERROR,
96
+ format(
97
+ LogMessageEnum::ErrorMessages::INVALID_TRACK_RETURNING_USER_VALUE,
98
+ file: FILE
99
+ )
100
+ )
101
+ @is_instance_valid = false
102
+ return
103
+ end
104
+ else
105
+ @should_track_returning_user = false
106
+ end
107
+
108
+ if options.key?(:goal_type_to_track)
109
+ if GOAL_TYPES.key? options[:goal_type_to_track]
110
+ @goal_type_to_track = options[:goal_type_to_track]
111
+ usage_stats[:gt] = 1
112
+ else
113
+ @logger.log(
114
+ LogLevelEnum::ERROR,
115
+ format(
116
+ LogMessageEnum::ErrorMessages::INVALID_GOAL_TYPE,
117
+ file: FILE
118
+ )
119
+ )
120
+ @is_instance_valid = false
121
+ return
122
+ end
123
+ else
124
+ @goal_type_to_track = 'ALL'
125
+ end
126
+
77
127
  @is_instance_valid = true
78
128
  @config = VWO::Services::SettingsFileProcessor.new(get_settings)
79
129
 
@@ -89,8 +139,54 @@ class VWO
89
139
  @config.process_settings_file
90
140
  @settings_file = @config.get_settings_file
91
141
 
142
+ @usage_stats = VWO::Services::UsageStats.new(usage_stats, @is_development_mode)
143
+
144
+ if options.key?(:batch_events)
145
+ if options[:batch_events].is_a?(Hash)
146
+ unless is_valid_batch_event_settings(options[:batch_events])
147
+ @is_instance_valid = false
148
+ return
149
+ end
150
+ @batch_event_dispatcher = VWO::Services::BatchEventsDispatcher.new
151
+ def dispatcher (events, callback)
152
+ @batch_event_dispatcher.dispatch(
153
+ {
154
+ ev: events
155
+ },
156
+ callback,
157
+ {
158
+ a: @account_id,
159
+ sd: SDK_NAME,
160
+ sv: SDK_VERSION,
161
+ env: @sdk_key
162
+ }.merge(@usage_stats.usage_stats)
163
+ )
164
+ end
165
+ @batch_events_queue = VWO::Services::BatchEventsQueue.new(
166
+ options[:batch_events].merge(
167
+ {
168
+ account_id: @account_id,
169
+ dispatcher: method(:dispatcher)
170
+ }
171
+ )
172
+ )
173
+ @batch_events_queue.flush(manual: true)
174
+ @batch_events = options[:batch_events]
175
+ else
176
+ @logger.log(
177
+ LogLevelEnum::ERROR,
178
+ format(
179
+ LogMessageEnum::ErrorMessages::EVENT_BATCHING_NOT_OBJECT,
180
+ file: FILE
181
+ )
182
+ )
183
+ @is_instance_valid = false
184
+ return
185
+ end
186
+ end
187
+
92
188
  # Assign VariationDecider to VWO
93
- @variation_decider = VWO::Core::VariationDecider.new(@settings_file, user_storage)
189
+ @variation_decider = VWO::Core::VariationDecider.new(@settings_file, user_storage, options)
94
190
 
95
191
  if is_development_mode
96
192
  @logger.log(
@@ -118,9 +214,55 @@ class VWO
118
214
 
119
215
  # VWO get_settings method to get settings for a particular account_id
120
216
  def get_settings(settings_file = nil)
121
- @settings ||=
122
- settings_file || VWO::Services::SettingsFileManager.new(@account_id, @sdk_key).get_settings_file
123
- @settings
217
+ @settings_file ||=
218
+ settings_file || @settings_file_manager.get_settings_file
219
+ @settings_file
220
+ end
221
+
222
+ # This API method: fetch the latest settings file and update it
223
+
224
+ # VWO get_settings method to get settings for a particular account_id
225
+ def get_and_update_settings_file
226
+
227
+ unless @is_instance_valid
228
+ @logger.log(
229
+ LogLevelEnum::ERROR,
230
+ format(
231
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
232
+ file: FILE,
233
+ api_name: ApiMethods.GET_AND_UPDATE_SETTINGS_FILE
234
+ )
235
+ )
236
+ return
237
+ end
238
+
239
+ latest_settings = @settings_file_manager.get_settings_file(true)
240
+ latest_settings = JSON.parse(latest_settings)
241
+ if latest_settings == @settings_file
242
+ @logger.log(
243
+ LogLevelEnum::INFO,
244
+ format(
245
+ LogMessageEnum::InfoMessages::SETTINGS_NOT_UPDATED,
246
+ api_name: ApiMethods::GET_AND_UPDATE_SETTINGS_FILE,
247
+ file: FILE
248
+ )
249
+ )
250
+ end
251
+
252
+ @config.update_settings_file(latest_settings)
253
+ @settings_file = @config.get_settings_file
254
+ @settings_file
255
+ rescue StandardError => e
256
+ @logger.log(
257
+ LogLevelEnum::ERROR,
258
+ format(
259
+ LogMessageEnum::ErrorMessages::API_NOT_WORKING,
260
+ file: FILE,
261
+ api_name: ApiMethods::GET_AND_UPDATE_SETTINGS_FILE,
262
+ exception: e
263
+ )
264
+ )
265
+ nil
124
266
  end
125
267
 
126
268
  # This API method: Gets the variation assigned for the user
@@ -136,34 +278,37 @@ class VWO
136
278
  #
137
279
  # @param[String] :campaign_key Unique campaign key
138
280
  # @param[String] :user_id ID assigned to a user
139
- # @param[Hash] :options Options for custom variables required for segmentation
281
+ # @param[Hash] :options Options for custom variables required for segmentation
140
282
  # @return[String|None] If variation is assigned then variation-name
141
283
  # otherwise null in case of user not becoming part
142
284
 
143
285
  def activate(campaign_key, user_id, options = {})
144
- # Retrieve custom variables
145
- custom_variables = options['custom_variables'] || options[:custom_variables]
146
-
147
- # Validate input parameters
148
- unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables))
286
+ unless @is_instance_valid
149
287
  @logger.log(
150
288
  LogLevelEnum::ERROR,
151
289
  format(
152
- LogMessageEnum::ErrorMessages::ACTIVATE_API_MISSING_PARAMS,
153
- api_name: ApiMethods::ACTIVATE,
154
- file: FILE
290
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
291
+ file: FILE,
292
+ api_name: ApiMethods::ACTIVATE
155
293
  )
156
294
  )
157
295
  return
158
296
  end
159
297
 
160
- unless @is_instance_valid
298
+ # Retrieve custom variables
299
+ custom_variables = options['custom_variables'] || options[:custom_variables]
300
+ variation_targeting_variables = options['variation_targeting_variables'] || options[:variation_targeting_variables]
301
+
302
+ should_track_returning_user = get_should_track_returning_user(options)
303
+ # Validate input parameters
304
+ unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables)) &&
305
+ (variation_targeting_variables.nil? || valid_hash?(variation_targeting_variables)) && [true, false].include?(should_track_returning_user)
161
306
  @logger.log(
162
307
  LogLevelEnum::ERROR,
163
308
  format(
164
- LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
165
- file: FILE,
166
- api_name: ApiMethods::ACTIVATE
309
+ LogMessageEnum::ErrorMessages::ACTIVATE_API_MISSING_PARAMS,
310
+ api_name: ApiMethods::ACTIVATE,
311
+ file: FILE
167
312
  )
168
313
  )
169
314
  return
@@ -208,11 +353,14 @@ class VWO
208
353
 
209
354
  # Once the matching RUNNING campaign is found, assign the
210
355
  # deterministic variation to the user_id provided
356
+
211
357
  variation = @variation_decider.get_variation(
212
358
  user_id,
213
359
  campaign,
360
+ ApiMethods::ACTIVATE,
214
361
  campaign_key,
215
- custom_variables
362
+ custom_variables,
363
+ variation_targeting_variables
216
364
  )
217
365
 
218
366
  # Check if variation_name has been assigned
@@ -229,15 +377,56 @@ class VWO
229
377
  return
230
378
  end
231
379
 
232
- # Variation found, dispatch it to server
233
- impression = create_impression(
234
- @settings_file,
235
- campaign['id'],
236
- variation['id'],
237
- user_id
238
- )
239
- @event_dispatcher.dispatch(impression)
240
- variation['name']
380
+ if is_eligible_to_send_impression(should_track_returning_user)
381
+ if defined?(@batch_events)
382
+ impression = create_bulk_event_impression(
383
+ @settings_file,
384
+ campaign['id'],
385
+ variation['id'],
386
+ user_id
387
+ )
388
+ @batch_events_queue.enqueue(impression)
389
+ else
390
+ # Variation found, dispatch it to server
391
+ impression = create_impression(
392
+ @settings_file,
393
+ campaign['id'],
394
+ variation['id'],
395
+ user_id,
396
+ @sdk_key,
397
+ nil, # goal_id
398
+ nil, # revenue
399
+ usage_stats: @usage_stats.usage_stats
400
+ )
401
+ if @event_dispatcher.dispatch(impression)
402
+ @logger.log(
403
+ LogLevelEnum::INFO,
404
+ format(
405
+ LogMessageEnum::InfoMessages::IMPRESSION_SUCCESS,
406
+ file: FILE,
407
+ sdk_key: @sdk_key,
408
+ account_id: @account_id,
409
+ campaign_id: campaign['id'],
410
+ variation_id: variation['id'],
411
+ end_point: EVENTS::TRACK_USER
412
+ )
413
+ )
414
+ end
415
+ end
416
+ variation['name']
417
+ else
418
+ @logger.log(
419
+ LogLevelEnum::INFO,
420
+ format(
421
+ LogMessageEnum::InfoMessages::USER_ALREADY_TRACKED,
422
+ file: FILE,
423
+ user_id: user_id,
424
+ campaign_key: campaign_key,
425
+ api_name: ApiMethods::ACTIVATE
426
+ )
427
+ )
428
+ nil
429
+ end
241
430
  rescue StandardError => e
242
431
  @logger.log(
243
432
  LogLevelEnum::ERROR,
@@ -248,7 +437,7 @@ class VWO
248
437
  exception: e
249
438
  )
250
439
  )
251
- nil
440
+ e
252
441
  end
253
442
 
254
443
  # This API method: Gets the variation name assigned for the
@@ -269,28 +458,30 @@ class VWO
269
458
  # Otherwise null in case of user not becoming part
270
459
  #
271
460
  def get_variation_name(campaign_key, user_id, options = {})
272
- # Retrieve custom variables
273
- custom_variables = options['custom_variables'] || options[:custom_variables]
274
-
275
- # Validate input parameters
276
- unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables))
461
+ unless @is_instance_valid
277
462
  @logger.log(
278
463
  LogLevelEnum::ERROR,
279
464
  format(
280
- LogMessageEnum::ErrorMessages::GET_VARIATION_NAME_API_INVALID_PARAMS,
281
- api_name: ApiMethods::GET_VARIATION_NAME,
282
- file: FILE
465
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
466
+ file: FILE,
467
+ api_name: ApiMethods::GET_VARIATION_NAME
283
468
  )
284
469
  )
285
470
  return
286
471
  end
287
- unless @is_instance_valid
472
+ # Retrieve custom variables
473
+ custom_variables = options['custom_variables'] || options[:custom_variables]
474
+ variation_targeting_variables = options['variation_targeting_variables'] || options[:variation_targeting_variables]
475
+
476
+ # Validate input parameters
477
+ unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables)) &&
478
+ (variation_targeting_variables.nil? || valid_hash?(variation_targeting_variables))
288
479
  @logger.log(
289
480
  LogLevelEnum::ERROR,
290
481
  format(
291
- LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
292
- file: FILE,
293
- api_name: ApiMethods::GET_VARIATION_NAME
482
+ LogMessageEnum::ErrorMessages::GET_VARIATION_NAME_API_INVALID_PARAMS,
483
+ api_name: ApiMethods::GET_VARIATION_NAME,
484
+ file: FILE
294
485
  )
295
486
  )
296
487
  return
@@ -330,7 +521,7 @@ class VWO
330
521
  return
331
522
  end
332
523
 
333
- variation = @variation_decider.get_variation(user_id, campaign, campaign_key, custom_variables)
524
+ variation = @variation_decider.get_variation(user_id, campaign, ApiMethods::GET_VARIATION_NAME, campaign_key, custom_variables, variation_targeting_variables)
334
525
 
335
526
  # Check if variation_name has been assigned
336
527
  unless valid_value?(variation)
@@ -372,33 +563,11 @@ class VWO
372
563
  # @param[String] :campaign_key Unique campaign key
373
564
  # @param[String] :user_id ID assigned to a user
374
565
  # @param[String] :goal_identifier Unique campaign's goal identifier
375
- # @param[Array|Hash] :args Contains revenue value and custom variables
566
+ # @param[Hash] :options Contains revenue value and custom variables
376
567
  # @param[Numeric|String] :revenue_value It is the revenue generated on triggering the goal
377
568
  #
378
- def track(campaign_key, user_id, goal_identifier, *args)
379
- if args[0].is_a?(Hash)
380
- revenue_value = args[0]['revenue_value'] || args[0][:revenue_value]
381
- custom_variables = args[0]['custom_variables'] || args[0][:custom_variables]
382
- elsif args.is_a?(Array)
383
- revenue_value = args[0]
384
- custom_variables = nil
385
- end
386
-
387
- # Check for valid args
388
- unless valid_string?(campaign_key) && valid_string?(user_id) && valid_string?(goal_identifier) &&
389
- (custom_variables.nil? || valid_hash?(custom_variables)) || (revenue_value.nil? || valid_basic_data_type?(revenue_value))
390
- # log invalid params
391
- @logger.log(
392
- LogLevelEnum::ERROR,
393
- format(
394
- LogMessageEnum::ErrorMessages::TRACK_API_INVALID_PARAMS,
395
- file: FILE,
396
- api_name: ApiMethods.TRACK
397
- )
398
- )
399
- return false
400
- end
401
569
 
570
+ def track(campaign_key, user_id, goal_identifier, options = {})
402
571
  unless @is_instance_valid
403
572
  @logger.log(
404
573
  LogLevelEnum::ERROR,
@@ -411,98 +580,187 @@ class VWO
411
580
  return false
412
581
  end
413
582
 
414
- # Get the campaign settings
415
- campaign = get_campaign(@settings_file, campaign_key)
583
+ revenue_value = options['revenue_value'] || options[:revenue_value]
584
+ custom_variables = options['custom_variables'] || options[:custom_variables]
585
+ variation_targeting_variables = options['variation_targeting_variables'] || options[:variation_targeting_variables]
586
+ should_track_returning_user = get_should_track_returning_user(options)
587
+ goal_type_to_track = get_goal_type_to_track(options)
416
588
 
417
- # Validate campaign
418
- if campaign.nil? || (campaign['status'] != STATUS_RUNNING)
419
- # log error
589
+ # Check for valid args
590
+ unless (valid_string?(campaign_key) || campaign_key.is_a?(Array) || campaign_key.nil?) && valid_string?(user_id) && valid_string?(goal_identifier) && (custom_variables.nil? || valid_hash?(custom_variables)) &&
591
+ (variation_targeting_variables.nil? || valid_hash?(variation_targeting_variables)) && [true, false].include?(should_track_returning_user) && (GOAL_TYPES.key? (goal_type_to_track))
592
+ # log invalid params
420
593
  @logger.log(
421
594
  LogLevelEnum::ERROR,
422
595
  format(
423
- LogMessageEnum::ErrorMessages::CAMPAIGN_NOT_RUNNING,
596
+ LogMessageEnum::ErrorMessages::TRACK_API_INVALID_PARAMS,
424
597
  file: FILE,
425
- campaign_key: campaign_key,
426
598
  api_name: ApiMethods::TRACK
427
599
  )
428
600
  )
429
601
  return false
430
602
  end
431
603
 
432
- campaign_type = campaign['type']
604
+ # Get campaigns settings
605
+ campaigns = get_campaigns(@settings_file, campaign_key, goal_identifier, goal_type_to_track)
433
606
 
434
- if campaign_type == CampaignTypes::FEATURE_ROLLOUT
435
- @logger.log(
436
- LogLevelEnum::ERROR,
437
- format(
438
- LogMessageEnum::ErrorMessages::INVALID_API,
439
- file: FILE,
440
- api_name: ApiMethods::TRACK,
441
- user_id: user_id,
442
- campaign_key: campaign_key,
443
- campaign_type: campaign_type
444
- )
445
- )
446
- return false
607
+ # Validate campaign
608
+ if campaigns.nil?
609
+ return nil
447
610
  end
448
611
 
449
- variation = @variation_decider.get_variation(user_id, campaign, campaign_key, custom_variables)
450
-
451
- if variation
452
- goal = get_campaign_goal(campaign, goal_identifier)
453
- if goal.nil?
454
- @logger.log(
455
- LogLevelEnum::ERROR,
456
- format(
457
- LogMessageEnum::ErrorMessages::TRACK_API_GOAL_NOT_FOUND,
458
- file: FILE,
459
- goal_identifier: goal_identifier,
460
- user_id: user_id,
461
- campaign_key: campaign_key,
462
- api_name: ApiMethods::TRACK
612
+ result = {}
613
+ campaigns.each do |campaign|
614
+ begin
615
+ campaign_type = campaign['type']
616
+
617
+ if campaign_type == CampaignTypes::FEATURE_ROLLOUT
618
+ @logger.log(
619
+ LogLevelEnum::ERROR,
620
+ format(
621
+ LogMessageEnum::ErrorMessages::INVALID_API,
622
+ file: FILE,
623
+ api_name: ApiMethods::TRACK,
624
+ user_id: user_id,
625
+ campaign_key: campaign['key'],
626
+ campaign_type: campaign_type
627
+ )
463
628
  )
464
- )
465
- return false
466
- elsif goal['type'] == GoalTypes::REVENUE && !valid_value?(revenue_value)
629
+ result[campaign['key']] = false
630
+ next
631
+ end
632
+
633
+ variation = @variation_decider.get_variation(user_id, campaign, ApiMethods::TRACK, campaign['key'], custom_variables, variation_targeting_variables, goal_identifier)
634
+
635
+ if variation
636
+ goal = get_campaign_goal(campaign, goal_identifier)
637
+ if goal.nil? || !goal["id"]
638
+ @logger.log(
639
+ LogLevelEnum::ERROR,
640
+ format(
641
+ LogMessageEnum::ErrorMessages::TRACK_API_GOAL_NOT_FOUND,
642
+ file: FILE,
643
+ goal_identifier: goal_identifier,
644
+ user_id: user_id,
645
+ campaign_key: campaign['key'],
646
+ api_name: ApiMethods::TRACK
647
+ )
648
+ )
649
+ result[campaign['key']] = false
650
+ next
651
+ elsif goal['type'] == GoalTypes::REVENUE && !valid_value?(revenue_value)
652
+ @logger.log(
653
+ LogLevelEnum::ERROR,
654
+ format(
655
+ LogMessageEnum::ErrorMessages::TRACK_API_REVENUE_NOT_PASSED_FOR_REVENUE_GOAL,
656
+ file: FILE,
657
+ user_id: user_id,
658
+ goal_identifier: goal_identifier,
659
+ campaign_key: campaign['key'],
660
+ api_name: ApiMethods::TRACK
661
+ )
662
+ )
663
+ result[campaign['key']] = false
664
+ next
665
+ elsif goal['type'] == GoalTypes::CUSTOM
666
+ revenue_value = nil
667
+ end
668
+
669
+ if variation['goal_identifier']
670
+ identifiers = variation['goal_identifier'].split(VWO_DELIMITER)
671
+ else
672
+ variation['goal_identifier'] = ''
673
+ identifiers = []
674
+ end
675
+
676
+ if !identifiers.include? goal_identifier
677
+ updated_goal_identifier = variation['goal_identifier']
678
+ updated_goal_identifier += VWO_DELIMITER + goal_identifier
679
+ @variation_decider.save_user_storage(user_id, campaign['key'], variation['name'], updated_goal_identifier) if variation['name']
680
+ # set variation at user storage
681
+ elsif !should_track_returning_user
682
+ @logger.log(
683
+ LogLevelEnum::INFO,
684
+ format(
685
+ LogMessageEnum::InfoMessages::GOAL_ALREADY_TRACKED,
686
+ file: FILE,
687
+ user_id: user_id,
688
+ campaign_key: campaign['key'],
689
+ goal_identifier: goal_identifier,
690
+ api_name: ApiMethods::TRACK
691
+ )
692
+ )
693
+ result[campaign['key']] = false
694
+ next
695
+ end
696
+
697
+ if defined?(@batch_events)
698
+ impression = create_bulk_event_impression(
699
+ @settings_file,
700
+ campaign['id'],
701
+ variation['id'],
702
+ user_id,
703
+ goal['id'],
704
+ revenue_value
705
+ )
706
+ @batch_events_queue.enqueue(impression)
707
+ else
708
+ impression = create_impression(
709
+ @settings_file,
710
+ campaign['id'],
711
+ variation['id'],
712
+ user_id,
713
+ @sdk_key,
714
+ goal['id'],
715
+ revenue_value
716
+ )
717
+ if @event_dispatcher.dispatch(impression)
718
+ @logger.log(
719
+ LogLevelEnum::INFO,
720
+ format(
721
+ LogMessageEnum::InfoMessages::IMPRESSION_SUCCESS,
722
+ file: FILE,
723
+ sdk_key: @sdk_key,
724
+ account_id: @account_id,
725
+ campaign_id: campaign['id'],
726
+ variation_id: variation['id'],
727
+ end_point: EVENTS::TRACK_GOAL
728
+ )
729
+ )
730
+ @logger.log(
731
+ LogLevelEnum::INFO,
732
+ format(
733
+ LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_IMPRESSION,
734
+ file: FILE,
735
+ sdk_key: @sdk_key,
736
+ campaign_id: impression[:experiment_id],
737
+ account_id: impression[:account_id],
738
+ variation_id: impression[:combination]
739
+ )
740
+ )
741
+ end
742
+ end
743
+ result[campaign['key']] = true
744
+ next
745
+ end
746
+ result[campaign['key']] = false
747
+ rescue StandardError => e
467
748
  @logger.log(
468
749
  LogLevelEnum::ERROR,
469
750
  format(
470
- LogMessageEnum::ErrorMessages::TRACK_API_REVENUE_NOT_PASSED_FOR_REVENUE_GOAL,
751
+ e.message,
471
752
  file: FILE,
472
- user_id: user_id,
473
- goal_identifier: goal_identifier,
474
- campaign_key: campaign_key,
475
- api_name: ApiMethods::TRACK
753
+ exception: e
476
754
  )
477
755
  )
478
- return false
479
- elsif goal['type'] == GoalTypes::CUSTOM
480
- revenue_value = nil
481
756
  end
482
- impression = create_impression(
483
- @settings_file,
484
- campaign['id'],
485
- variation['id'],
486
- user_id,
487
- goal['id'],
488
- revenue_value
489
- )
490
- @event_dispatcher.dispatch(impression)
757
+ end
491
758
 
492
- @logger.log(
493
- LogLevelEnum::INFO,
494
- format(
495
- LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_IMPRESSION,
496
- file: FILE,
497
- campaign_id: impression[:experiment_id],
498
- user_id: impression[:uId],
499
- account_id: impression[:account_id],
500
- variation_id: impression[:combination]
501
- )
502
- )
503
- return true
759
+ if result.length() == 0
760
+ return nil
504
761
  end
505
- false
762
+
763
+ result
506
764
  rescue StandardError => e
507
765
  @logger.log(
508
766
  LogLevelEnum::ERROR,
@@ -530,28 +788,40 @@ class VWO
530
788
  # @return[Boolean] true if user becomes part of feature test/rollout, otherwise false.
531
789
 
532
790
  def feature_enabled?(campaign_key, user_id, options = {})
533
- # Retrieve custom variables
534
- custom_variables = options['custom_variables'] || options[:custom_variables]
535
-
536
- # Validate input parameters
537
- unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables))
791
+ unless @is_instance_valid
538
792
  @logger.log(
539
793
  LogLevelEnum::ERROR,
540
794
  format(
541
- LogMessageEnum::ErrorMessages::IS_FEATURE_ENABLED_API_INVALID_PARAMS,
542
- api_name: ApiMethods::IS_FEATURE_ENABLED,
543
- file: FILE
795
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
796
+ file: FILE,
797
+ api_name: ApiMethods::IS_FEATURE_ENABLED
544
798
  )
545
799
  )
546
800
  return false
547
801
  end
548
- unless @is_instance_valid
802
+
803
+ # Retrieve custom variables
804
+ custom_variables = options['custom_variables'] || options[:custom_variables]
805
+ variation_targeting_variables = options['variation_targeting_variables'] || options[:variation_targeting_variables]
806
+ should_track_returning_user = get_should_track_returning_user(options)
807
+ @logger.log(
808
+ LogLevelEnum::INFO,
809
+ format(
810
+ LogMessageEnum::InfoMessages::API_CALLED,
811
+ file: FILE,
812
+ api_name: ApiMethods::IS_FEATURE_ENABLED,
813
+ user_id: user_id
814
+ )
815
+ )
816
+ # Validate input parameters
817
+ unless valid_string?(campaign_key) && valid_string?(user_id) && (custom_variables.nil? || valid_hash?(custom_variables)) &&
818
+ (variation_targeting_variables.nil? || valid_hash?(variation_targeting_variables)) && [true, false].include?(should_track_returning_user)
549
819
  @logger.log(
550
820
  LogLevelEnum::ERROR,
551
821
  format(
552
- LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
553
- file: FILE,
554
- api_name: ApiMethods::IS_FEATURE_ENABLED
822
+ LogMessageEnum::ErrorMessages::IS_FEATURE_ENABLED_API_INVALID_PARAMS,
823
+ api_name: ApiMethods::IS_FEATURE_ENABLED,
824
+ file: FILE
555
825
  )
556
826
  )
557
827
  return false
@@ -594,57 +864,84 @@ class VWO
594
864
  end
595
865
 
596
866
  # Get variation
597
- variation = @variation_decider.get_variation(user_id, campaign, campaign_key, custom_variables)
867
+ variation = @variation_decider.get_variation(user_id, campaign, ApiMethods::IS_FEATURE_ENABLED, campaign_key, custom_variables, variation_targeting_variables)
598
868
 
599
869
  # If no variation, did not become part of feature_test/rollout
600
870
  return false unless variation
601
871
 
602
872
  # if campaign type is feature_test Send track call to server
603
873
  if campaign_type == CampaignTypes::FEATURE_TEST
604
- impression = create_impression(
605
- @settings_file,
606
- campaign['id'],
607
- variation['id'],
608
- user_id
609
- )
874
+ if is_eligible_to_send_impression(should_track_returning_user)
875
+ if defined?(@batch_events)
876
+ impression = create_bulk_event_impression(
877
+ @settings_file,
878
+ campaign['id'],
879
+ variation['id'],
880
+ user_id
881
+ )
882
+ @batch_events_queue.enqueue(impression)
883
+ else
884
+ impression = create_impression(
885
+ @settings_file,
886
+ campaign['id'],
887
+ variation['id'],
888
+ user_id,
889
+ @sdk_key,
890
+ goal_id: nil,
891
+ revenue: nil,
892
+ usage_stats: @usage_stats.usage_stats
893
+ )
610
894
 
611
- @event_dispatcher.dispatch(impression)
612
- @logger.log(
613
- LogLevelEnum::INFO,
614
- format(
615
- LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_IMPRESSION,
616
- file: FILE,
617
- campaign_id: impression[:experiment_id],
618
- user_id: impression[:uId],
619
- account_id: impression[:account_id],
620
- variation_id: impression[:combination]
621
- )
622
- )
623
- result = variation['isFeatureEnabled']
624
- if result
625
- @logger.log(
626
- LogLevelEnum::INFO,
627
- format(
628
- LogMessageEnum::InfoMessages::FEATURE_ENABLED_FOR_USER,
629
- file: FILE,
630
- user_id: user_id,
631
- feature_key: campaign_key,
632
- api_name: ApiMethods::IS_FEATURE_ENABLED
895
+ @event_dispatcher.dispatch(impression)
896
+ @logger.log(
897
+ LogLevelEnum::INFO,
898
+ format(
899
+ LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_IMPRESSION,
900
+ file: FILE,
901
+ campaign_id: impression[:experiment_id],
902
+ sdk_key: @sdk_key,
903
+ account_id: impression[:account_id],
904
+ variation_id: impression[:combination]
905
+ )
633
906
  )
634
- )
907
+ end
908
+ result = variation['isFeatureEnabled']
909
+ if result
910
+ @logger.log(
911
+ LogLevelEnum::INFO,
912
+ format(
913
+ LogMessageEnum::InfoMessages::FEATURE_ENABLED_FOR_USER,
914
+ file: FILE,
915
+ user_id: user_id,
916
+ feature_key: campaign_key,
917
+ api_name: ApiMethods::IS_FEATURE_ENABLED
918
+ )
919
+ )
920
+ else
921
+ @logger.log(
922
+ LogLevelEnum::INFO,
923
+ format(
924
+ LogMessageEnum::InfoMessages::FEATURE_NOT_ENABLED_FOR_USER,
925
+ file: FILE,
926
+ user_id: user_id,
927
+ feature_key: campaign_key,
928
+ api_name: ApiMethods::IS_FEATURE_ENABLED
929
+ )
930
+ )
931
+ end
932
+ return result
635
933
  else
636
934
  @logger.log(
637
935
  LogLevelEnum::INFO,
638
936
  format(
639
- LogMessageEnum::InfoMessages::FEATURE_NOT_ENABLED_FOR_USER,
937
+ LogMessageEnum::InfoMessages::USER_ALREADY_TRACKED,
640
938
  file: FILE,
641
939
  user_id: user_id,
642
- feature_key: campaign_key,
940
+ campaign_key: campaign_key,
643
941
  api_name: ApiMethods::IS_FEATURE_ENABLED
644
942
  )
645
943
  )
646
944
  end
647
- return result
648
945
  end
649
946
  true
650
947
  rescue StandardError => e
@@ -680,29 +977,30 @@ class VWO
680
977
  #
681
978
 
682
979
  def get_feature_variable_value(campaign_key, variable_key, user_id, options = {})
683
- # Retrieve custom variables
684
- custom_variables = options['custom_variables'] || options[:custom_variables]
685
-
686
- unless valid_string?(campaign_key) && valid_string?(variable_key) && valid_string?(user_id) &&
687
- (custom_variables.nil? || valid_hash?(custom_variables))
980
+ unless @is_instance_valid
688
981
  @logger.log(
689
982
  LogLevelEnum::ERROR,
690
983
  format(
691
- LogMessageEnum::ErrorMessages::GET_FEATURE_VARIABLE_VALUE_API_INVALID_PARAMS,
984
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
692
985
  file: FILE,
693
- api_name: ApiMethods::GET_FEATURE_VARIABLE_VALUE
986
+ api_name: ApiMethods.GET_FEATURE_VARIABLE_VALUE
694
987
  )
695
988
  )
696
989
  return
697
990
  end
698
991
 
699
- unless @is_instance_valid
992
+ # Retrieve custom variables
993
+ custom_variables = options['custom_variables'] || options[:custom_variables]
994
+ variation_targeting_variables = options['variation_targeting_variables'] || options[:variation_targeting_variables]
995
+
996
+ unless valid_string?(campaign_key) && valid_string?(variable_key) && valid_string?(user_id) &&
997
+ (custom_variables.nil? || valid_hash?(custom_variables)) && (variation_targeting_variables.nil? || valid_hash?(variation_targeting_variables))
700
998
  @logger.log(
701
999
  LogLevelEnum::ERROR,
702
1000
  format(
703
- LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
1001
+ LogMessageEnum::ErrorMessages::GET_FEATURE_VARIABLE_VALUE_API_INVALID_PARAMS,
704
1002
  file: FILE,
705
- api_name: ApiMethods.GET_FEATURE_VARIABLE_VALUE
1003
+ api_name: ApiMethods::GET_FEATURE_VARIABLE_VALUE
706
1004
  )
707
1005
  )
708
1006
  return
@@ -743,7 +1041,7 @@ class VWO
743
1041
  return
744
1042
  end
745
1043
 
746
- variation = @variation_decider.get_variation(user_id, campaign, campaign_key, custom_variables)
1044
+ variation = @variation_decider.get_variation(user_id, campaign, ApiMethods::GET_FEATURE_VARIABLE_VALUE, campaign_key, custom_variables, variation_targeting_variables)
747
1045
 
748
1046
  # Check if variation has been assigned to user
749
1047
  return unless variation
@@ -832,6 +1130,18 @@ class VWO
832
1130
  # @return true if call is made successfully, else false
833
1131
 
834
1132
  def push(tag_key, tag_value, user_id)
1133
+ unless @is_instance_valid
1134
+ @logger.log(
1135
+ LogLevelEnum::ERROR,
1136
+ format(
1137
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
1138
+ file: FILE,
1139
+ api_name: ApiMethods.PUSH
1140
+ )
1141
+ )
1142
+ return
1143
+ end
1144
+
835
1145
  unless valid_string?(tag_key) && valid_string?(tag_value) && valid_string?(user_id)
836
1146
  @logger.log(
837
1147
  LogLevelEnum::ERROR,
@@ -872,32 +1182,105 @@ class VWO
872
1182
  return false
873
1183
  end
874
1184
 
875
- impression = get_url_params(@settings_file, tag_key, tag_value, user_id)
876
-
877
- @event_dispatcher.dispatch(impression)
1185
+ if defined?(@batch_events)
1186
+ impression = get_batch_event_url_params(@settings_file, tag_key, tag_value, user_id)
1187
+ @batch_events_queue.enqueue(impression)
1188
+ else
1189
+ impression = get_url_params(@settings_file, tag_key, tag_value, user_id, @sdk_key)
1190
+ @event_dispatcher.dispatch(impression)
878
1191
 
1192
+ @logger.log(
1193
+ LogLevelEnum::INFO,
1194
+ format(
1195
+ LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_PUSH_API,
1196
+ file: FILE,
1197
+ sdk_key: @sdk_key,
1198
+ u: impression['u'],
1199
+ account_id: impression['account_id'],
1200
+ tags: impression['tags']
1201
+ )
1202
+ )
1203
+ end
1204
+ true
1205
+ rescue StandardError => e
879
1206
  @logger.log(
880
- LogLevelEnum::INFO,
1207
+ LogLevelEnum::ERROR,
881
1208
  format(
882
- LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_PUSH_API,
1209
+ LogMessageEnum::ErrorMessages::API_NOT_WORKING,
883
1210
  file: FILE,
884
- u: impression['u'],
885
- user_id: impression['uId'],
886
- account_id: impression['account_id'],
887
- tags: impression['tags']
1211
+ api_name: ApiMethods::PUSH,
1212
+ exception: e
888
1213
  )
889
1214
  )
890
- true
1215
+ false
1216
+ end
1217
+
1218
+ def get_should_track_returning_user(options)
1219
+ if !options.key?(:should_track_returning_user)
1220
+ options[:should_track_returning_user] = @should_track_returning_user
1221
+ elsif ![true, false].include?(options[:should_track_returning_user])
1222
+ @logger.log(
1223
+ LogLevelEnum::ERROR,
1224
+ format(
1225
+ LogMessageEnum::ErrorMessages::INVALID_TRACK_RETURNING_USER_VALUE,
1226
+ file: FILE
1227
+ )
1228
+ )
1229
+ end
1230
+ options[:should_track_returning_user]
1231
+ end
1232
+
1233
+ def is_eligible_to_send_impression(should_track_returning_user = false)
1234
+ !@user_storage || !@variation_decider.has_stored_variation || should_track_returning_user
1235
+ end
1236
+
1237
+ def flush_events
1238
+ unless @is_instance_valid
1239
+ @logger.log(
1240
+ LogLevelEnum::ERROR,
1241
+ format(
1242
+ LogMessageEnum::ErrorMessages::API_CONFIG_CORRUPTED,
1243
+ file: FILE,
1244
+ api_name: ApiMethods::FLUSH_EVENTS
1245
+ )
1246
+ )
1247
+ return
1248
+ end
1249
+ result = @batch_events_queue.flush(manual: true)
1250
+ @batch_events_queue.kill_thread
1251
+ result
891
1252
  rescue StandardError => e
892
1253
  @logger.log(
893
1254
  LogLevelEnum::ERROR,
894
1255
  format(
895
1256
  LogMessageEnum::ErrorMessages::API_NOT_WORKING,
896
1257
  file: FILE,
897
- api_name: ApiMethods::PUSH,
1258
+ api_name: ApiMethods::FLUSH_EVENTS,
898
1259
  exception: e
899
1260
  )
900
1261
  )
901
1262
  false
902
1263
  end
1264
+
1265
+ def get_goal_type_to_track(options)
1266
+ goal_type_to_track = nil
1267
+ if !options.key?(:goal_type_to_track)
1268
+ if @goal_type_to_track
1269
+ goal_type_to_track = @goal_type_to_track
1270
+ else
1271
+ goal_type_to_track = GOAL_TYPES['ALL']
1272
+ end
1273
+ elsif GOAL_TYPES.key? options[:goal_type_to_track]
1274
+ goal_type_to_track = options[:goal_type_to_track]
1275
+ else
1276
+ @logger.log(
1277
+ LogLevelEnum::ERROR,
1278
+ format(
1279
+ LogMessageEnum::ErrorMessages::INVALID_GOAL_TYPE,
1280
+ file: FILE
1281
+ )
1282
+ )
1283
+ end
1284
+ goal_type_to_track
1285
+ end
903
1286
  end