vwo-sdk 1.5.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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