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.
- checksums.yaml +5 -5
- data/lib/vwo.rb +1020 -84
- data/lib/vwo/constants.rb +64 -13
- data/lib/vwo/core/bucketer.rb +10 -14
- data/lib/vwo/core/variation_decider.rb +391 -80
- data/lib/vwo/enums.rb +113 -10
- data/lib/vwo/logger.rb +9 -3
- data/lib/vwo/schemas/settings_file.rb +1 -3
- data/lib/vwo/services/batch_events_dispatcher.rb +110 -0
- data/lib/vwo/services/batch_events_queue.rb +175 -0
- data/lib/vwo/services/event_dispatcher.rb +3 -17
- data/lib/vwo/services/hooks_manager.rb +36 -0
- data/lib/vwo/services/operand_evaluator.rb +122 -0
- data/lib/vwo/services/segment_evaluator.rb +88 -0
- data/lib/vwo/services/settings_file_manager.rb +11 -9
- data/lib/vwo/services/settings_file_processor.rb +6 -3
- data/lib/vwo/services/usage_stats.rb +29 -0
- data/lib/vwo/user_storage.rb +1 -3
- data/lib/vwo/utils/campaign.rb +151 -22
- data/lib/vwo/utils/custom_dimensions.rb +72 -0
- data/lib/vwo/utils/feature.rb +56 -0
- data/lib/vwo/utils/function.rb +6 -3
- data/lib/vwo/utils/impression.rb +76 -7
- data/lib/vwo/utils/request.rb +15 -3
- data/lib/vwo/utils/segment.rb +116 -0
- data/lib/vwo/utils/uuid.rb +3 -5
- data/lib/vwo/utils/validations.rb +96 -4
- metadata +30 -8
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.
|
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
|
-
|
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
|
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
|
data/lib/vwo/core/bucketer.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
|
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 =
|
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
|
-
|
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(
|
134
|
-
|
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 =
|
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
|
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
|
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
|
-
|
59
|
-
|
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
|
-
|
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
|
-
|
278
|
+
variation = get_variation_allotted(user_id, campaign)
|
80
279
|
|
81
|
-
if
|
82
|
-
save_user_storage(user_id, campaign_key,
|
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:
|
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
|
-
|
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
|
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
|
327
|
+
return
|
118
328
|
end
|
119
329
|
|
120
330
|
if @bucketer.user_part_of_campaign?(user_id, campaign)
|
121
|
-
|
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:
|
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
|
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
|
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
|
-
|
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
|
-
|
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::
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|