vwo-sdk 1.3.0 → 1.14.1

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