vwo-sdk 1.3.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/vwo/constants.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2019 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.
@@ -12,8 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- # frozen_string_literal: true
16
-
17
15
  class VWO
18
16
  module CONSTANTS
19
17
  API_VERSION = 1
@@ -22,23 +20,35 @@ class VWO
22
20
  MAX_TRAFFIC_PERCENT = 100
23
21
  MAX_TRAFFIC_VALUE = 10_000
24
22
  STATUS_RUNNING = 'RUNNING'
23
+ # rubocop:disable Style/ExpandPathArguments
25
24
  LIBRARY_PATH = File.expand_path('../..', __FILE__)
25
+ # rubocop:enable Style/ExpandPathArguments
26
26
  HTTP_PROTOCOL = 'http://'
27
27
  HTTPS_PROTOCOL = 'https://'
28
28
  URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'
29
- SDK_VERSION = '1.3.0'
29
+ SDK_VERSION = '1.14.1'
30
30
  SDK_NAME = 'ruby'
31
+ VWO_DELIMITER = '_vwo_'
32
+ MAX_EVENTS_PER_REQUEST = 5000
33
+ MIN_EVENTS_PER_REQUEST = 1
34
+ DEFAULT_EVENTS_PER_REQUEST = 100
35
+ DEFAULT_REQUEST_TIME_INTERVAL = 600 # 10 * 60(secs) = 600 secs i.e. 10 minutes
36
+ MIN_REQUEST_TIME_INTERVAL = 2
31
37
 
32
38
  module ENDPOINTS
33
39
  BASE_URL = 'dev.visualwebsiteoptimizer.com'
34
- ACCOUNT_SETTINGS = '/server-side/settings'
40
+ SETTINGS_URL = '/server-side/settings'
41
+ WEBHOOK_SETTINGS_URL = '/server-side/pull'
35
42
  TRACK_USER = '/server-side/track-user'
36
43
  TRACK_GOAL = '/server-side/track-goal'
44
+ PUSH = '/server-side/push'
45
+ BATCH_EVENTS = '/server-side/batch-events'
37
46
  end
38
47
 
39
48
  module EVENTS
40
49
  TRACK_USER = 'track-user'
41
50
  TRACK_GOAL = 'track-goal'
51
+ PUSH = 'push'
42
52
  end
43
53
 
44
54
  module DATATYPE
@@ -48,16 +58,57 @@ class VWO
48
58
  BOOLEAN = 'boolean'
49
59
  end
50
60
 
51
- module APIMETHODS
52
- CREATE_INSTANCE = 'CREATE_INSTANCE'
53
- ACTIVATE = 'ACTIVATE'
54
- GET_VARIATION = 'GET_VARIATION'
55
- TRACK = 'TRACK'
56
- end
57
-
58
- module GOALTYPES
61
+ module GoalTypes
59
62
  REVENUE = 'REVENUE_TRACKING'
60
63
  CUSTOM = 'CUSTOM_GOAL'
61
64
  end
65
+
66
+ module VariableTypes
67
+ STRING = 'string'
68
+ INTEGER = 'integer'
69
+ DOUBLE = 'double'
70
+ BOOLEAN = 'boolean'
71
+ end
72
+
73
+ module Hooks
74
+ DECISION_TYPES = {
75
+ 'CAMPAIGN_DECISION' => 'CAMPAIGN_DECISION'
76
+ }
77
+ end
78
+
79
+ RUBY_VARIABLE_TYPES = {
80
+ 'string' => [String],
81
+ 'integer' => [Integer],
82
+ 'double' => [Float],
83
+ 'boolean' => [TrueClass, FalseClass]
84
+ }
85
+
86
+ GOAL_TYPES = {
87
+ 'REVENUE' => 'REVENUE_TRACKING',
88
+ 'CUSTOM' => 'CUSTOM_GOAL',
89
+ 'ALL' => 'ALL'
90
+ }
91
+
92
+ module ApiMethods
93
+ ACTIVATE = 'activate'
94
+ GET_VARIATION_NAME = 'get_variation_name'
95
+ TRACK = 'track'
96
+ IS_FEATURE_ENABLED = 'is_feature_enabled'
97
+ GET_FEATURE_VARIABLE_VALUE = 'get_feature_variable_value'
98
+ PUSH = 'push'
99
+ GET_AND_UPDATE_SETTINGS_FILE = 'get_and_update_settings_file'
100
+ FLUSH_EVENTS = 'flush_events'
101
+ end
102
+
103
+ module PushApi
104
+ TAG_VALUE_LENGTH = 255
105
+ TAG_KEY_LENGTH = 255
106
+ end
107
+
108
+ module CampaignTypes
109
+ VISUAL_AB = 'VISUAL_AB'
110
+ FEATURE_TEST = 'FEATURE_TEST'
111
+ FEATURE_ROLLOUT = 'FEATURE_ROLLOUT'
112
+ end
62
113
  end
63
114
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2019 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.
@@ -12,8 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- # frozen_string_literal: true
16
-
17
15
  require 'murmurhash3'
18
16
  require_relative '../logger'
19
17
  require_relative '../enums'
@@ -43,7 +41,7 @@ class VWO
43
41
  # @param[String] :user_id The unique ID assigned to a user
44
42
  # @param[Dict] :campaign For getting traffic allotted to the campaign
45
43
  # @return[Boolean] If User is a part of Campaign or not
46
- #
44
+
47
45
  def user_part_of_campaign?(user_id, campaign)
48
46
  unless valid_value?(user_id)
49
47
  @logger.log(
@@ -62,7 +60,6 @@ class VWO
62
60
  end
63
61
 
64
62
  traffic_allocation = campaign['percentTraffic']
65
-
66
63
  value_assigned_to_user = get_bucket_value_for_user(user_id)
67
64
  is_user_part = (value_assigned_to_user != 0) && value_assigned_to_user <= traffic_allocation
68
65
  @logger.log(
@@ -98,9 +95,9 @@ class VWO
98
95
  end
99
96
 
100
97
  hash_value = MurmurHash3::V32.str_hash(user_id, SEED_VALUE) & U_MAX_32_BIT
101
- normalize = MAX_TRAFFIC_VALUE / campaign['percentTraffic']
98
+ normalize = MAX_TRAFFIC_VALUE.to_f / campaign['percentTraffic']
102
99
  multiplier = normalize / 100
103
- bucket_value = generate_bucket_value(
100
+ bucket_value = get_bucket_value(
104
101
  hash_value,
105
102
  MAX_TRAFFIC_VALUE,
106
103
  multiplier
@@ -118,10 +115,9 @@ class VWO
118
115
  hash_value: hash_value
119
116
  )
120
117
  )
121
- get_variation(campaign, bucket_value)
122
- end
123
118
 
124
- private
119
+ get_variation(campaign['variations'], bucket_value)
120
+ end
125
121
 
126
122
  # Returns the Variation by checking the Start and End
127
123
  # Bucket Allocations of each Variation
@@ -130,8 +126,8 @@ class VWO
130
126
  # @param[Integer] :bucket_value The bucket Value of the user
131
127
  # @return[Hash|nil] Variation data allotted to the user or None if not
132
128
  #
133
- def get_variation(campaign, bucket_value)
134
- campaign['variations'].find do |variation|
129
+ def get_variation(variations, bucket_value)
130
+ variations.find do |variation|
135
131
  (variation['start_variation_allocation']..variation['end_variation_allocation']).cover?(bucket_value)
136
132
  end
137
133
  end
@@ -144,7 +140,7 @@ class VWO
144
140
  # (between 1 to $this->$MAX_TRAFFIC_PERCENT)
145
141
  def get_bucket_value_for_user(user_id)
146
142
  hash_value = MurmurHash3::V32.str_hash(user_id, SEED_VALUE) & U_MAX_32_BIT
147
- bucket_value = generate_bucket_value(hash_value, MAX_TRAFFIC_PERCENT)
143
+ bucket_value = get_bucket_value(hash_value, MAX_TRAFFIC_PERCENT)
148
144
 
149
145
  @logger.log(
150
146
  LogLevelEnum::DEBUG,
@@ -167,7 +163,7 @@ class VWO
167
163
  # @param[Integer] :multiplier
168
164
  # @return[Integer] Bucket Value of the User
169
165
  #
170
- def generate_bucket_value(hash_value, max_value, multiplier = 1)
166
+ def get_bucket_value(hash_value, max_value, multiplier = 1)
171
167
  ratio = hash_value.to_f / MAX_HASH_VALUE
172
168
  multiplied_value = (max_value * ratio + 1) * multiplier
173
169
  multiplied_value.to_i
@@ -1,4 +1,4 @@
1
- # Copyright 2019 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.
@@ -12,22 +12,26 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- # frozen_string_literal: true
16
-
17
15
  require_relative '../logger'
18
16
  require_relative '../enums'
19
17
  require_relative '../utils/campaign'
18
+ require_relative '../services/segment_evaluator'
20
19
  require_relative '../utils/validations'
21
20
  require_relative 'bucketer'
21
+ require_relative '../constants'
22
+ require_relative '../services/hooks_manager'
23
+ require_relative '../utils/uuid'
22
24
 
23
25
  class VWO
24
26
  module Core
25
27
  class VariationDecider
26
- attr_reader :user_storage_service
28
+ attr_reader :user_storage_service, :has_stored_variation, :hooks_manager
27
29
 
28
30
  include VWO::Enums
29
31
  include VWO::Utils::Campaign
30
32
  include VWO::Utils::Validations
33
+ include VWO::CONSTANTS
34
+ include VWO::Utils::UUID
31
35
 
32
36
  FILE = FileNameEnum::VariationDecider
33
37
 
@@ -35,34 +39,125 @@ class VWO
35
39
  # @param[Hash] - Settings file
36
40
  # @param[Class] - Class instance having the capability of
37
41
  # get and save.
38
- def initialize(settings_file, user_storage_service = nil)
42
+ def initialize(settings_file, user_storage_service = nil, options = {})
39
43
  @logger = VWO::Logger.get_instance
40
44
  @user_storage_service = user_storage_service
41
- # Check if user_storage_service provided is valid or not
42
- @user_storage_service = user_storage_service
43
45
  @bucketer = VWO::Core::Bucketer.new
44
46
  @settings_file = settings_file
47
+ @segment_evaluator = VWO::Services::SegmentEvaluator.new
48
+ @hooks_manager = VWO::Services::HooksManager.new(options)
45
49
  end
46
50
 
47
51
  # Returns variation for the user for the passed campaign-key
52
+ # Check if Whitelisting is applicable, evaluate it, if any eligible variation is found,return, otherwise skip it
48
53
  # Check in User Storage, if user found, validate variation and return
49
54
  # Otherwise, proceed with variation assignment logic
50
55
  #
51
56
  #
52
57
  # @param[String] :user_id The unique ID assigned to User
53
- # @param[Hash] :campaign Campaign hash itslef
58
+ # @param[Hash] :campaign Campaign hash itself
59
+ # @param[String] :campaign_key The unique ID of the campaign passed
60
+ # @param[String] :goal_identifier The unique campaign's goal identifier
54
61
  # @return[String,String] ({variation_id, variation_name}|Nil): Tuple of
55
62
  # variation_id and variation_name if variation allotted, else nil
56
63
 
57
- def get_variation(user_id, campaign)
58
- if campaign
59
- campaign_key = campaign['key']
64
+ def get_variation(user_id, campaign, api_name, campaign_key, custom_variables = {}, variation_targeting_variables = {}, goal_identifier = '')
65
+ campaign_key ||= campaign['key']
66
+
67
+ return unless campaign
68
+
69
+ @has_stored_variation = false
70
+ decision = {
71
+ :campaign_id => campaign['id'],
72
+ :campaign_key => campaign_key,
73
+ :campaign_type => campaign['type'],
74
+ # campaign segmentation conditions
75
+ :custom_variables => custom_variables,
76
+ # event name
77
+ :event => Hooks::DECISION_TYPES['CAMPAIGN_DECISION'],
78
+ # goal tracked in case of track API
79
+ :goal_identifier => goal_identifier,
80
+ # campaign whitelisting flag
81
+ :is_forced_variation_enabled => campaign['isForcedVariationEnabled'] ? campaign['isForcedVariationEnabled'] : false,
82
+ :sdk_version => SDK_VERSION,
83
+ # API name which triggered the event
84
+ :source => api_name,
85
+ # Passed in API
86
+ :user_id => user_id,
87
+ # Campaign Whitelisting conditions
88
+ :variation_targeting_variables => variation_targeting_variables,
89
+ :is_user_whitelisted => false,
90
+ :from_user_storage_service => false,
91
+ :is_feature_enabled => true,
92
+ # VWO generated UUID based on passed UserId and Account ID
93
+ :vwo_user_id => generator_for(user_id, @settings_file['accountId'])
94
+ }
95
+
96
+ if campaign['isForcedVariationEnabled']
97
+ variation = evaluate_whitelisting(
98
+ user_id,
99
+ campaign,
100
+ api_name,
101
+ campaign_key,
102
+ variation_targeting_variables
103
+ )
104
+ status = if variation
105
+ StatusEnum::PASSED
106
+ else
107
+ StatusEnum::FAILED
108
+ end
109
+
110
+ @logger.log(
111
+ LogLevelEnum::INFO,
112
+ format(
113
+ LogMessageEnum::InfoMessages::SEGMENTATION_STATUS,
114
+ file: FILE,
115
+ campaign_key: campaign_key,
116
+ user_id: user_id,
117
+ status: status,
118
+ custom_variables: variation_targeting_variables,
119
+ variation_name: status == StatusEnum::PASSED ? "and #{variation['name']} is Assigned" : ' ',
120
+ segmentation_type: SegmentationTypeEnum::WHITELISTING,
121
+ api_name: api_name
122
+ )
123
+ )
124
+
125
+ if variation
126
+ if campaign['type'] == CampaignTypes::VISUAL_AB || campaign['type'] == CampaignTypes::FEATURE_TEST
127
+ decision[:variation_name] = variation['name']
128
+ decision[:variation_id] = variation['id']
129
+ if campaign['type'] == CampaignTypes::FEATURE_TEST
130
+ decision[:is_feature_enabled] = variation['isFeatureEnabled']
131
+ elsif campaign['type'] == CampaignTypes::VISUAL_AB
132
+ decision[:is_user_whitelisted] = !!variation['name']
133
+ end
134
+ end
135
+ @hooks_manager.execute(decision)
136
+ end
137
+
138
+ return variation if variation && variation['name']
139
+ else
140
+ @logger.log(
141
+ LogLevelEnum::INFO,
142
+ format(
143
+ LogMessageEnum::InfoMessages::WHITELISTING_SKIPPED,
144
+ file: FILE,
145
+ campaign_key: campaign_key,
146
+ user_id: user_id,
147
+ api_name: api_name
148
+ )
149
+ )
60
150
  end
61
151
 
62
152
  user_campaign_map = get_user_storage(user_id, campaign_key)
63
153
  variation = get_stored_variation(user_id, campaign_key, user_campaign_map) if valid_hash?(user_campaign_map)
154
+ variation = variation.dup # deep copy
64
155
 
65
156
  if variation
157
+ if valid_string?(user_campaign_map['goal_identifier']) && api_name == ApiMethods::TRACK
158
+ variation['goal_identifier'] = user_campaign_map['goal_identifier']
159
+ end
160
+ @has_stored_variation = true
66
161
  @logger.log(
67
162
  LogLevelEnum::INFO,
68
163
  format(
@@ -73,13 +168,117 @@ class VWO
73
168
  variation_name: variation['name']
74
169
  )
75
170
  )
76
- return variation['id'], variation['name']
171
+ decision[:from_user_storage_service] = !!variation['name']
172
+ if variation
173
+ if campaign['type'] == CampaignTypes::VISUAL_AB || campaign['type'] == CampaignTypes::FEATURE_TEST
174
+ decision[:variation_name] = variation['name']
175
+ decision[:variation_id] = variation['id']
176
+ if campaign['type'] == CampaignTypes::FEATURE_TEST
177
+ decision[:is_feature_enabled] = variation['isFeatureEnabled']
178
+ end
179
+ end
180
+ @hooks_manager.execute(decision)
181
+ end
182
+ return variation
183
+ else
184
+ @logger.log(
185
+ LogLevelEnum::DEBUG,
186
+ format(
187
+ LogMessageEnum::DebugMessages::NO_STORED_VARIATION,
188
+ file: FILE,
189
+ campaign_key: campaign_key,
190
+ user_id: user_id
191
+ )
192
+ )
193
+
194
+ if ([ApiMethods::TRACK, ApiMethods::GET_VARIATION_NAME, ApiMethods::GET_FEATURE_VARIABLE_VALUE].include? api_name) &&
195
+ @user_storage_service && campaign['type'] != CampaignTypes::FEATURE_ROLLOUT
196
+ @logger.log(
197
+ LogLevelEnum::DEBUG,
198
+ format(
199
+ LogMessageEnum::DebugMessages::CAMPAIGN_NOT_ACTIVATED,
200
+ file: FILE,
201
+ campaign_key: campaign_key,
202
+ user_id: user_id,
203
+ api_name: api_name
204
+ )
205
+ )
206
+
207
+ @logger.log(
208
+ LogLevelEnum::INFO,
209
+ format(
210
+ LogMessageEnum::InfoMessages::CAMPAIGN_NOT_ACTIVATED,
211
+ file: FILE,
212
+ campaign_key: campaign_key,
213
+ user_id: user_id,
214
+ api_name: api_name,
215
+ reason: api_name == ApiMethods::TRACK ? 'track it' : 'get the decision/value'
216
+ )
217
+ )
218
+ return
219
+ end
220
+ end
221
+
222
+ # Pre-segmentation
223
+
224
+ segments = get_segments(campaign)
225
+ is_valid_segments = valid_value?(segments)
226
+
227
+ if is_valid_segments
228
+ unless custom_variables
229
+ @logger.log(
230
+ LogLevelEnum::INFO,
231
+ format(
232
+ LogMessageEnum::InfoMessages::NO_CUSTOM_VARIABLES,
233
+ file: FILE,
234
+ campaign_key: campaign_key,
235
+ user_id: user_id,
236
+ api_name: api_name
237
+ )
238
+ )
239
+ custom_variables = {}
240
+ end
241
+ unless @segment_evaluator.evaluate(campaign_key, user_id, segments, custom_variables)
242
+ @logger.log(
243
+ LogLevelEnum::INFO,
244
+ format(
245
+ LogMessageEnum::InfoMessages::USER_FAILED_SEGMENTATION,
246
+ file: FileNameEnum::SegmentEvaluator,
247
+ user_id: user_id,
248
+ campaign_key: campaign_key,
249
+ custom_variables: custom_variables
250
+ )
251
+ )
252
+ return
253
+ end
254
+ @logger.log(
255
+ LogLevelEnum::INFO,
256
+ format(
257
+ LogMessageEnum::InfoMessages::USER_PASSED_SEGMENTATION,
258
+ file: FileNameEnum::SegmentEvaluator,
259
+ user_id: user_id,
260
+ campaign_key: campaign_key,
261
+ custom_variables: custom_variables
262
+ )
263
+ )
264
+ else
265
+ @logger.log(
266
+ LogLevelEnum::INFO,
267
+ format(
268
+ LogMessageEnum::InfoMessages::SKIPPING_SEGMENTATION,
269
+ file: FILE,
270
+ campaign_key: campaign_key,
271
+ user_id: user_id,
272
+ api_name: api_name,
273
+ variation: ''
274
+ )
275
+ )
77
276
  end
78
277
 
79
- variation_id, variation_name = get_variation_allotted(user_id, campaign)
278
+ variation = get_variation_allotted(user_id, campaign)
80
279
 
81
- if variation_name
82
- save_user_storage(user_id, campaign_key, variation_name) if variation_name
280
+ if variation && variation['name']
281
+ save_user_storage(user_id, campaign_key, variation['name'], goal_identifier) if variation['name']
83
282
 
84
283
  @logger.log(
85
284
  LogLevelEnum::INFO,
@@ -88,7 +287,8 @@ class VWO
88
287
  file: FILE,
89
288
  campaign_key: campaign_key,
90
289
  user_id: user_id,
91
- variation_name: variation_name
290
+ variation_name: variation['name'],
291
+ campaign_type: campaign['type']
92
292
  )
93
293
  )
94
294
  else
@@ -97,39 +297,50 @@ class VWO
97
297
  format(LogMessageEnum::InfoMessages::NO_VARIATION_ALLOCATED, file: FILE, campaign_key: campaign_key, user_id: user_id)
98
298
  )
99
299
  end
100
- [variation_id, variation_name]
300
+
301
+ if variation
302
+ if campaign['type'] == CampaignTypes::VISUAL_AB || campaign['type'] == CampaignTypes::FEATURE_TEST
303
+ decision[:variation_name] = variation['name']
304
+ decision[:variation_id] = variation['id']
305
+ if campaign['type'] == CampaignTypes::FEATURE_TEST
306
+ decision[:is_feature_enabled] = variation['isFeatureEnabled']
307
+ end
308
+ end
309
+ @hooks_manager.execute(decision)
310
+ end
311
+ variation
101
312
  end
102
313
 
103
314
  # Returns the Variation Alloted for required campaign
104
315
  #
105
- # @param[String] :user_id The unique ID assigned to User
316
+ # @param[String] :user_id The unique key assigned to User
106
317
  # @param[Hash] :campaign Campaign hash for Unique campaign key
107
318
  #
108
319
  # @return[Hash]
109
320
 
110
321
  def get_variation_allotted(user_id, campaign)
111
- variation_id, variation_name = nil
112
322
  unless valid_value?(user_id)
113
323
  @logger.log(
114
324
  LogLevelEnum::ERROR,
115
325
  format(LogMessageEnum::ErrorMessages::INVALID_USER_ID, file: FILE, user_id: user_id, method: 'get_variation_alloted')
116
326
  )
117
- return variation_id, variation_name
327
+ return
118
328
  end
119
329
 
120
330
  if @bucketer.user_part_of_campaign?(user_id, campaign)
121
- variation_id, variation_name = get_variation_of_campaign_for_user(user_id, campaign)
331
+ variation = get_variation_of_campaign_for_user(user_id, campaign)
122
332
  @logger.log(
123
333
  LogLevelEnum::DEBUG,
124
334
  format(
125
335
  LogMessageEnum::DebugMessages::GOT_VARIATION_FOR_USER,
126
336
  file: FILE,
127
- variation_name: variation_name,
337
+ variation_name: variation['name'],
128
338
  user_id: user_id,
129
339
  campaign_key: campaign['key'],
130
340
  method: 'get_variation_allotted'
131
341
  )
132
342
  )
343
+ variation
133
344
  else
134
345
  # not part of campaign
135
346
  @logger.log(
@@ -142,8 +353,8 @@ class VWO
142
353
  method: 'get_variation_allotted'
143
354
  )
144
355
  )
356
+ nil
145
357
  end
146
- [variation_id, variation_name]
147
358
  end
148
359
 
149
360
  # Assigns variation to a particular user depending on the campaign PercentTraffic.
@@ -151,6 +362,7 @@ class VWO
151
362
  # @param[String] :user_id The unique ID assigned to a user
152
363
  # @param[Hash] :campaign The Campaign of which user is to be made a part of
153
364
  # @return[Hash] Variation allotted to User
365
+
154
366
  def get_variation_of_campaign_for_user(user_id, campaign)
155
367
  unless campaign
156
368
  @logger.log(
@@ -161,7 +373,7 @@ class VWO
161
373
  method: 'get_variation_of_campaign_for_user'
162
374
  )
163
375
  )
164
- return nil, nil
376
+ return nil
165
377
  end
166
378
 
167
379
  variation = @bucketer.bucket_user_to_variation(user_id, campaign)
@@ -177,7 +389,7 @@ class VWO
177
389
  campaign_key: campaign['key']
178
390
  )
179
391
  )
180
- return variation['id'], variation['name']
392
+ return variation
181
393
  end
182
394
 
183
395
  @logger.log(
@@ -189,11 +401,155 @@ class VWO
189
401
  campaign_key: campaign['key']
190
402
  )
191
403
  )
192
- [nil, nil]
404
+ nil
405
+ end
406
+
407
+ # If UserStorageService is provided, save the assigned variation
408
+ #
409
+ # @param[String] :user_id Unique user identifier
410
+ # @param[String] :campaign_key Unique campaign identifier
411
+ # @param[String] :variation_name Variation identifier
412
+ # @param[String] :goal_identifier The unique campaign's goal identifier
413
+ # @return[Boolean] true if found otherwise false
414
+
415
+ def save_user_storage(user_id, campaign_key, variation_name, goal_identifier)
416
+ unless @user_storage_service
417
+ @logger.log(
418
+ LogLevelEnum::DEBUG,
419
+ format(LogMessageEnum::DebugMessages::NO_USER_STORAGE_SERVICE_SAVE, file: FILE)
420
+ )
421
+ return false
422
+ end
423
+ new_campaign_user_mapping = {}
424
+ new_campaign_user_mapping['campaign_key'] = campaign_key
425
+ new_campaign_user_mapping['user_id'] = user_id
426
+ new_campaign_user_mapping['variation_name'] = variation_name
427
+ if !goal_identifier.empty?
428
+ new_campaign_user_mapping['goal_identifier'] = goal_identifier
429
+ end
430
+
431
+ @user_storage_service.set(new_campaign_user_mapping)
432
+
433
+ @logger.log(
434
+ LogLevelEnum::INFO,
435
+ format(LogMessageEnum::InfoMessages::SAVING_DATA_USER_STORAGE_SERVICE, file: FILE, user_id: user_id)
436
+ )
437
+ true
438
+ rescue StandardError
439
+ @logger.log(
440
+ LogLevelEnum::ERROR,
441
+ format(LogMessageEnum::ErrorMessages::SAVE_USER_STORAGE_SERVICE_FAILED, file: FILE, user_id: user_id)
442
+ )
443
+ false
193
444
  end
194
445
 
195
446
  private
196
447
 
448
+ # Evaluate all the variations in the campaign to find
449
+ #
450
+ # @param[String] :user_id The unique key assigned to User
451
+ # @param[Hash] :campaign Campaign hash for Unique campaign key
452
+ # @param[String] :api_name The key Passed to identify the calling API
453
+ # @param[String] :campaign_key Unique campaign key
454
+ # @param[Hash] :variation_targeting_variables Key/value pair of Whitelisting Custom Attributes
455
+ #
456
+ # @return[Hash]
457
+
458
+ def evaluate_whitelisting(user_id, campaign, api_name, campaign_key, variation_targeting_variables = {})
459
+ if variation_targeting_variables.nil?
460
+ variation_targeting_variables = { '_vwo_user_id' => user_id }
461
+ else
462
+ variation_targeting_variables['_vwo_user_id'] = user_id
463
+ end
464
+ targeted_variations = []
465
+
466
+ campaign['variations'].each do |variation|
467
+ segments = get_segments(variation)
468
+ is_valid_segments = valid_value?(segments)
469
+ if is_valid_segments
470
+ if @segment_evaluator.evaluate(campaign_key, user_id, segments, variation_targeting_variables)
471
+ targeted_variations.push(variation)
472
+ status = StatusEnum::PASSED
473
+ else
474
+ status = StatusEnum::FAILED
475
+ end
476
+ @logger.log(
477
+ LogLevelEnum::DEBUG,
478
+ format(
479
+ LogMessageEnum::DebugMessages::SEGMENTATION_STATUS,
480
+ file: FILE,
481
+ campaign_key: campaign_key,
482
+ user_id: user_id,
483
+ status: status,
484
+ custom_variables: variation_targeting_variables,
485
+ variation_name: variation['name'],
486
+ segmentation_type: SegmentationTypeEnum::WHITELISTING,
487
+ api_name: api_name
488
+ )
489
+ )
490
+ else
491
+ @logger.log(
492
+ LogLevelEnum::DEBUG,
493
+ format(
494
+ LogMessageEnum::InfoMessages::SKIPPING_SEGMENTATION,
495
+ file: FILE,
496
+ campaign_key: campaign_key,
497
+ user_id: user_id,
498
+ api_name: api_name,
499
+ variation: variation['name']
500
+ )
501
+ )
502
+ end
503
+ end
504
+
505
+ if targeted_variations.length > 1
506
+ targeted_variations_deep_clone = Marshal.load(Marshal.dump(targeted_variations))
507
+ scale_variation_weights(targeted_variations_deep_clone)
508
+ current_allocation = 0
509
+ targeted_variations_deep_clone.each do |variation|
510
+ step_factor = get_variation_bucketing_range(variation['weight'])
511
+ if step_factor > 0
512
+ start_range = current_allocation + 1
513
+ end_range = current_allocation + step_factor
514
+ variation['start_variation_allocation'] = start_range
515
+ variation['end_variation_allocation'] = end_range
516
+ current_allocation += step_factor
517
+ else
518
+ variation['start_variation_allocation'] = -1
519
+ variation['end_variation_allocation'] = -1
520
+ end
521
+ end
522
+ whitelisted_variation = @bucketer.get_variation(
523
+ targeted_variations_deep_clone,
524
+ @bucketer.get_bucket_value_for_user(
525
+ user_id
526
+ )
527
+ )
528
+ else
529
+ whitelisted_variation = targeted_variations[0]
530
+ end
531
+ whitelisted_variation
532
+ end
533
+
534
+ # It extracts the weights from all the variations inside the campaign
535
+ # and scales them so that the total sum of eligible variations' weights become 100%
536
+ #
537
+ # 1. variations
538
+
539
+ def scale_variation_weights(variations)
540
+ total_weight = variations.reduce(0) { |final_weight, variation| final_weight + variation['weight'].to_f }
541
+ if total_weight == 0
542
+ weight = 100 / variations.length
543
+ variations.each do |variation|
544
+ variation['weight'] = weight
545
+ end
546
+ else
547
+ variations.each do |variation|
548
+ variation['weight'] = (variation['weight'] / total_weight) * 100
549
+ end
550
+ end
551
+ end
552
+
197
553
  # Get the UserStorageData after looking up into get method
198
554
  # Being provided via UserStorageService
199
555
  #
@@ -238,70 +594,25 @@ class VWO
238
594
  # @return[Object, nil] if found then variation settings object otherwise None
239
595
 
240
596
  def get_stored_variation(user_id, campaign_key, user_campaign_map)
241
- if user_campaign_map[campaign_key] == campaign_key
242
- variation_name = user_campaign_map[:variationName]
243
- @logger.log(
244
- LogLevelEnum::DEBUG,
245
- format(
246
- LogMessageEnum::DebugMessages::GETTING_STORED_VARIATION,
247
- file: FILE,
248
- campaign_key: campaign_key,
249
- user_id: user_id,
250
- variation_name: variation_name
251
- )
252
- )
253
- return get_campaign_variation(
254
- @settings_file,
255
- campaign_key,
256
- variation_name
257
- )
258
- end
597
+ return unless user_campaign_map['campaign_key'] == campaign_key
259
598
 
599
+ variation_name = user_campaign_map['variation_name']
260
600
  @logger.log(
261
601
  LogLevelEnum::DEBUG,
262
602
  format(
263
- LogMessageEnum::DebugMessages::NO_STORED_VARIATION,
603
+ LogMessageEnum::DebugMessages::GETTING_STORED_VARIATION,
264
604
  file: FILE,
265
605
  campaign_key: campaign_key,
266
- user_id: user_id
606
+ user_id: user_id,
607
+ variation_name: variation_name
267
608
  )
268
609
  )
269
- nil
270
- end
271
610
 
272
- # If UserStorageService is provided, save the assigned variation
273
- #
274
- # @param[String] :user_id Unique user identifier
275
- # @param[String] :campaign_key Unique campaign identifier
276
- # @param[String] :variation_name Variation identifier
277
- # @return[Boolean] true if found otherwise false
278
-
279
- def save_user_storage(user_id, campaign_key, variation_name)
280
- unless @user_storage_service
281
- @logger.log(
282
- LogLevelEnum::DEBUG,
283
- format(LogMessageEnum::DebugMessages::NO_USER_STORAGE_SERVICE_SAVE, file: FILE)
284
- )
285
- return false
286
- end
287
- new_campaign_user_mapping = {}
288
- new_campaign_user_mapping['campaign_key'] = campaign_key
289
- new_campaign_user_mapping['user_id'] = user_id
290
- new_campaign_user_mapping['variation_name'] = variation_name
291
-
292
- @user_storage_service.set(new_campaign_user_mapping)
293
-
294
- @logger.log(
295
- LogLevelEnum::INFO,
296
- format(LogMessageEnum::InfoMessages::SAVING_DATA_USER_STORAGE_SERVICE, file: FILE, user_id: user_id)
297
- )
298
- true
299
- rescue StandardError
300
- @logger.log(
301
- LogLevelEnum::ERROR,
302
- format(LogMessageEnum::ErrorMessages::SAVE_USER_STORAGE_SERVICE_FAILED, file: FILE, user_id: user_id)
611
+ get_campaign_variation(
612
+ @settings_file,
613
+ campaign_key,
614
+ variation_name
303
615
  )
304
- false
305
616
  end
306
617
  end
307
618
  end