vwo-sdk 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/vwo.rb +348 -0
- data/lib/vwo/constants.rb +63 -0
- data/lib/vwo/core/bucketer.rb +177 -0
- data/lib/vwo/core/variation_decider.rb +308 -0
- data/lib/vwo/enums.rb +115 -0
- data/lib/vwo/logger.rb +37 -0
- data/lib/vwo/schemas/settings_file.rb +102 -0
- data/lib/vwo/services/event_dispatcher.rb +82 -0
- data/lib/vwo/services/settings_file_manager.rb +84 -0
- data/lib/vwo/services/settings_file_processor.rb +54 -0
- data/lib/vwo/user_storage.rb +36 -0
- data/lib/vwo/utils/campaign.rb +125 -0
- data/lib/vwo/utils/function.rb +39 -0
- data/lib/vwo/utils/impression.rb +98 -0
- data/lib/vwo/utils/request.rb +31 -0
- data/lib/vwo/utils/uuid.rb +93 -0
- data/lib/vwo/utils/validations.rb +56 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2555f32eb2068e63dea737bdf3937708a5ef3e1e
|
4
|
+
data.tar.gz: dd662a2ac80ba5dc6892375c15b3d13e2e0ed3a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2dae59f19f9342856a343e5be3359b487069db19f4503398b577eed09c9e1e6ff98c37923c53b1fca91b5da5d66ad2d7a2414c582e0b02b3cb78f529a80a1568
|
7
|
+
data.tar.gz: 1d1778ccde23e98efed73462dca543c84c12ccb2a7cf4cb18e129f63514268742afc4a0d7a394b16476dadef08eb2d0bbd47b312a13afce23f1c66c23f5a3a99
|
data/lib/vwo.rb
ADDED
@@ -0,0 +1,348 @@
|
|
1
|
+
# Copyright 2019 Wingify Software Pvt. Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
require_relative 'vwo/services/settings_file_manager'
|
18
|
+
require_relative 'vwo/services/event_dispatcher'
|
19
|
+
require_relative 'vwo/services/settings_file_processor'
|
20
|
+
require_relative 'vwo/logger'
|
21
|
+
require_relative 'vwo/enums'
|
22
|
+
require_relative 'vwo/utils/campaign'
|
23
|
+
require_relative 'vwo/utils/impression'
|
24
|
+
require_relative 'vwo/constants'
|
25
|
+
require_relative 'vwo/core/variation_decider'
|
26
|
+
|
27
|
+
|
28
|
+
# VWO main file
|
29
|
+
class VWO
|
30
|
+
attr_accessor :is_instance_valid
|
31
|
+
|
32
|
+
include Enums
|
33
|
+
include Utils::Validations
|
34
|
+
include Utils::Campaign
|
35
|
+
include Utils::Impression
|
36
|
+
include CONSTANTS
|
37
|
+
|
38
|
+
FILE = FileNameEnum::VWO
|
39
|
+
|
40
|
+
# Initializes and expose APIs
|
41
|
+
#
|
42
|
+
# @param[Numeric|String] :account_id Account Id in VWO
|
43
|
+
# @param[String] :sdk_key Unique sdk key for user
|
44
|
+
# @param[Object] :logger Optional - should have log method defined
|
45
|
+
# @param[Object] :user_storage Optional - to store and manage user data mapping
|
46
|
+
# @param[Boolean] :is_development_mode To specify whether the request
|
47
|
+
# to our server should be sent or not.
|
48
|
+
# @param[String] :settings_file Settings-file data
|
49
|
+
|
50
|
+
def initialize(
|
51
|
+
account_id,
|
52
|
+
sdk_key,
|
53
|
+
logger = nil,
|
54
|
+
user_storage = nil,
|
55
|
+
is_development_mode = false,
|
56
|
+
settings_file = nil
|
57
|
+
)
|
58
|
+
@account_id = account_id
|
59
|
+
@sdk_key = sdk_key
|
60
|
+
@user_storage = user_storage
|
61
|
+
@is_development_mode = is_development_mode
|
62
|
+
@logger = VWO::Logger.get_instance(logger)
|
63
|
+
|
64
|
+
unless valid_settings_file?(get_settings(settings_file))
|
65
|
+
@logger.log(
|
66
|
+
LogLevelEnum::ERROR,
|
67
|
+
format(LogMessageEnum::ErrorMessages::SETTINGS_FILE_CORRUPTED, file: FILE)
|
68
|
+
)
|
69
|
+
@is_instance_valid = false
|
70
|
+
return
|
71
|
+
end
|
72
|
+
@is_instance_valid = true
|
73
|
+
@config = VWO::Services::SettingsFileProcessor.new(get_settings)
|
74
|
+
|
75
|
+
@logger.log(
|
76
|
+
LogLevelEnum::DEBUG,
|
77
|
+
format(LogMessageEnum::DebugMessages::VALID_CONFIGURATION, file: FILE)
|
78
|
+
)
|
79
|
+
|
80
|
+
# Process the settings file
|
81
|
+
@config.process_settings_file
|
82
|
+
@settings_file = @config.get_settings_file
|
83
|
+
|
84
|
+
# Assign VariationDecider to VWO
|
85
|
+
@variation_decider = VWO::Core::VariationDecider.new(@settings_file, user_storage)
|
86
|
+
|
87
|
+
if is_development_mode
|
88
|
+
@logger.log(
|
89
|
+
LogLevelEnum::DEBUG,
|
90
|
+
format(LogMessageEnum::DebugMessages::SET_DEVELOPMENT_MODE, file: FILE)
|
91
|
+
)
|
92
|
+
end
|
93
|
+
# Assign event dispatcher
|
94
|
+
@event_dispatcher = VWO::Services::EventDispatcher.new(is_development_mode)
|
95
|
+
|
96
|
+
# Successfully initialized VWO SDK
|
97
|
+
@logger.log(
|
98
|
+
LogLevelEnum::DEBUG,
|
99
|
+
format(LogMessageEnum::DebugMessages::SDK_INITIALIZED, file: FILE)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public Methods
|
104
|
+
|
105
|
+
# VWO get_settings method to get settings for a particular account_id
|
106
|
+
def get_settings(settings_file = nil)
|
107
|
+
@settings ||=
|
108
|
+
settings_file || VWO::Services::SettingsFileManager.new(@account_id, @sdk_key).get_settings_file
|
109
|
+
@settings
|
110
|
+
end
|
111
|
+
|
112
|
+
# This API method: Gets the variation assigned for the user
|
113
|
+
# For the campaign and send the metrics to VWO server
|
114
|
+
#
|
115
|
+
# 1. Validates the arguments being passed
|
116
|
+
# 2. Checks if user is eligible to get bucketed into the campaign,
|
117
|
+
# 3. Assigns the deterministic variation to the user(based on userId),
|
118
|
+
# If user becomes part of campaign
|
119
|
+
# If UserStorage is used, it will look into it for the
|
120
|
+
# Variation and if found, no further processing is done
|
121
|
+
# 4. Sends an impression call to VWO server to track user
|
122
|
+
#
|
123
|
+
# @param[String] :campaign_key Unique campaign key
|
124
|
+
# @param[String] :user_id ID assigned to a user
|
125
|
+
# @return[String|None] If variation is assigned then variation-name
|
126
|
+
# otherwise null in case of user not becoming part
|
127
|
+
|
128
|
+
def activate(campaign_key, user_id)
|
129
|
+
# Validate input parameters
|
130
|
+
unless valid_string?(campaign_key) && valid_string?(user_id)
|
131
|
+
@logger.log(
|
132
|
+
LogLevelEnum::ERROR,
|
133
|
+
format(LogMessageEnum::ErrorMessages::ACTIVATE_API_MISSING_PARAMS, file: FILE)
|
134
|
+
)
|
135
|
+
return
|
136
|
+
end
|
137
|
+
|
138
|
+
unless @is_instance_valid
|
139
|
+
@logger.log(
|
140
|
+
LogLevelEnum::ERROR,
|
141
|
+
format(LogMessageEnum::ErrorMessages::ACTIVATE_API_CONFIG_CORRUPTED, file: FILE)
|
142
|
+
)
|
143
|
+
return
|
144
|
+
end
|
145
|
+
|
146
|
+
# Get the campaign settings
|
147
|
+
campaign = get_campaign(@settings_file, campaign_key)
|
148
|
+
|
149
|
+
# Validate campaign
|
150
|
+
unless campaign && campaign['status'] == STATUS_RUNNING
|
151
|
+
# Log Campaign as invalid
|
152
|
+
@logger.log(
|
153
|
+
LogLevelEnum::ERROR,
|
154
|
+
format(LogMessageEnum::ErrorMessages::CAMPAIGN_NOT_RUNNING, file: FILE, campaign_key: campaign_key, api: 'activate')
|
155
|
+
)
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
# Once the matching RUNNING campaign is found, assign the
|
160
|
+
# deterministic variation to the user_id provided
|
161
|
+
variation_id, variation_name = @variation_decider.get_variation(
|
162
|
+
user_id,
|
163
|
+
campaign
|
164
|
+
)
|
165
|
+
|
166
|
+
# Check if variation_name has been assigned
|
167
|
+
unless valid_value?(variation_name)
|
168
|
+
@logger.log(
|
169
|
+
LogLevelEnum::INFO,
|
170
|
+
format(LogMessageEnum::InfoMessages::INVALID_VARIATION_KEY, file: FILE, user_id: user_id, campaign_key: campaign_key)
|
171
|
+
)
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
# Variation found, dispatch it to server
|
176
|
+
impression = create_impression(
|
177
|
+
@settings_file,
|
178
|
+
campaign['id'],
|
179
|
+
variation_id,
|
180
|
+
user_id
|
181
|
+
)
|
182
|
+
@event_dispatcher.dispatch(impression)
|
183
|
+
variation_name
|
184
|
+
end
|
185
|
+
|
186
|
+
# This API method: Gets the variation name assigned for the
|
187
|
+
# user for the campaign
|
188
|
+
#
|
189
|
+
# 1. Validates the arguments being passed
|
190
|
+
# 2. Checks if user is eligible to get bucketed into the campaign,
|
191
|
+
# 3. Assigns the deterministic variation to the user(based on user_id),
|
192
|
+
# If user becomes part of campaign
|
193
|
+
# If UserStorage is used, it will look into it for the
|
194
|
+
# variation and if found, no further processing is done
|
195
|
+
#
|
196
|
+
# @param[String] :campaign_key Unique campaign key
|
197
|
+
# @param[String] :user_id ID assigned to a user
|
198
|
+
#
|
199
|
+
# @@return[String|Nil] If variation is assigned then variation-name
|
200
|
+
# Otherwise null in case of user not becoming part
|
201
|
+
#
|
202
|
+
def get_variation_name(campaign_key, user_id)
|
203
|
+
# Check for valid arguments
|
204
|
+
unless valid_string?(campaign_key) && valid_string?(user_id)
|
205
|
+
# log invalid params
|
206
|
+
@logger.log(
|
207
|
+
LogLevelEnum::ERROR,
|
208
|
+
format(LogMessageEnum::ErrorMessages::GET_VARIATION_NAME_API_MISSING_PARAMS, file: FILE)
|
209
|
+
)
|
210
|
+
return
|
211
|
+
end
|
212
|
+
|
213
|
+
unless @is_instance_valid
|
214
|
+
@logger.log(
|
215
|
+
LogLevelEnum::ERROR,
|
216
|
+
format(LogMessageEnum::ErrorMessages::ACTIVATE_API_CONFIG_CORRUPTED, file: FILE)
|
217
|
+
)
|
218
|
+
return
|
219
|
+
end
|
220
|
+
|
221
|
+
# Get the campaign settings
|
222
|
+
campaign = get_campaign(@settings_file, campaign_key)
|
223
|
+
|
224
|
+
# Validate campaign
|
225
|
+
if campaign.nil? || campaign['status'] != STATUS_RUNNING
|
226
|
+
@logger.log(
|
227
|
+
LogLevelEnum::ERROR,
|
228
|
+
format(LogMessageEnum::ErrorMessages::CAMPAIGN_NOT_RUNNING, file: FILE, campaign_key: campaign_key, api: 'get_variation')
|
229
|
+
)
|
230
|
+
return
|
231
|
+
end
|
232
|
+
|
233
|
+
_variation_id, variation_name = @variation_decider.get_variation(
|
234
|
+
user_id,
|
235
|
+
campaign
|
236
|
+
)
|
237
|
+
|
238
|
+
# Check if variation_name has been assigned
|
239
|
+
unless valid_value?(variation_name)
|
240
|
+
# log invalid variation key
|
241
|
+
@logger.log(
|
242
|
+
LogLevelEnum::INFO,
|
243
|
+
format(LogMessageEnum::InfoMessages::INVALID_VARIATION_KEY, file: FILE, user_id: user_id, campaign_key: campaign_key)
|
244
|
+
)
|
245
|
+
return
|
246
|
+
end
|
247
|
+
|
248
|
+
variation_name
|
249
|
+
end
|
250
|
+
|
251
|
+
# This API method: Marks the conversion of the campaign
|
252
|
+
# for a particular goal
|
253
|
+
# 1. validates the arguments being passed
|
254
|
+
# 2. Checks if user is eligible to get bucketed into the campaign,
|
255
|
+
# 3. Gets the assigned deterministic variation to the
|
256
|
+
# user(based on user_d), if user becomes part of campaign
|
257
|
+
# 4. Sends an impression call to VWO server to track goal data
|
258
|
+
#
|
259
|
+
# @param[String] :campaign_key Unique campaign key
|
260
|
+
# @param[String] :user_id ID assigned to a user
|
261
|
+
# @param[String] :goal_identifier Unique campaign's goal identifier
|
262
|
+
# @param[Numeric|String] :revenue_value Revenue value for revenue-type goal
|
263
|
+
#
|
264
|
+
def track(campaign_key, user_id, goal_identifier, *args)
|
265
|
+
if args[0].is_a?(Hash)
|
266
|
+
revenue_value = args[0]['revenue_value']
|
267
|
+
elsif args.is_a?(Array)
|
268
|
+
revenue_value = args[0]
|
269
|
+
end
|
270
|
+
|
271
|
+
# Check for valid args
|
272
|
+
unless valid_string?(campaign_key) && valid_string?(user_id) && valid_string?(goal_identifier)
|
273
|
+
# log invalid params
|
274
|
+
@logger.log(
|
275
|
+
LogLevelEnum::ERROR,
|
276
|
+
format(LogMessageEnum::ErrorMessages::TRACK_API_MISSING_PARAMS, file: FILE)
|
277
|
+
)
|
278
|
+
return false
|
279
|
+
end
|
280
|
+
|
281
|
+
unless @is_instance_valid
|
282
|
+
@logger.log(
|
283
|
+
LogLevelEnum::ERROR,
|
284
|
+
format(LogMessageEnum::ErrorMessages::ACTIVATE_API_CONFIG_CORRUPTED, file: FILE)
|
285
|
+
)
|
286
|
+
return false
|
287
|
+
end
|
288
|
+
|
289
|
+
# Get the campaign settings
|
290
|
+
campaign = get_campaign(@settings_file, campaign_key)
|
291
|
+
|
292
|
+
# Validate campaign
|
293
|
+
if campaign.nil? || campaign['status'] != STATUS_RUNNING
|
294
|
+
# log error
|
295
|
+
@logger.log(
|
296
|
+
LogLevelEnum::ERROR,
|
297
|
+
format(LogMessageEnum::ErrorMessages::CAMPAIGN_NOT_RUNNING, file: FILE, campaign_key: campaign_key, api: 'track')
|
298
|
+
)
|
299
|
+
return false
|
300
|
+
end
|
301
|
+
|
302
|
+
campaign_id = campaign['id']
|
303
|
+
variation_id, variation_name = @variation_decider.get_variation_allotted(user_id, campaign)
|
304
|
+
|
305
|
+
if variation_name
|
306
|
+
goal = get_campaign_goal(@settings_file, campaign['key'], goal_identifier)
|
307
|
+
|
308
|
+
if goal.nil?
|
309
|
+
@logger.log(
|
310
|
+
LogLevelEnum::ERROR,
|
311
|
+
format(
|
312
|
+
LogMessageEnum::ErrorMessages::TRACK_API_GOAL_NOT_FOUND,
|
313
|
+
file: FILE, goal_identifier: goal_identifier,
|
314
|
+
user_id: user_id,
|
315
|
+
campaign_key: campaign_key
|
316
|
+
)
|
317
|
+
)
|
318
|
+
return false
|
319
|
+
elsif goal['type'] == GOALTYPES::REVENUE && !valid_value?(revenue_value)
|
320
|
+
@logger.log(
|
321
|
+
LogLevelEnum::ERROR,
|
322
|
+
format(
|
323
|
+
LogMessageEnum::ErrorMessages::TRACK_API_REVENUE_NOT_PASSED_FOR_REVENUE_GOAL,
|
324
|
+
file: FILE,
|
325
|
+
user_id: user_id,
|
326
|
+
goal_identifier: goal_identifier,
|
327
|
+
campaign_key: campaign_key
|
328
|
+
)
|
329
|
+
)
|
330
|
+
return false
|
331
|
+
end
|
332
|
+
|
333
|
+
revenue_value = nil if goal['type'] == GOALTYPES::CUSTOM
|
334
|
+
|
335
|
+
impression = create_impression(
|
336
|
+
@settings_file,
|
337
|
+
campaign_id,
|
338
|
+
variation_id,
|
339
|
+
user_id,
|
340
|
+
goal['id'],
|
341
|
+
revenue_value
|
342
|
+
)
|
343
|
+
@event_dispatcher.dispatch(impression)
|
344
|
+
return true
|
345
|
+
end
|
346
|
+
false
|
347
|
+
end
|
348
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2019 Wingify Software Pvt. Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
class VWO
|
18
|
+
module CONSTANTS
|
19
|
+
API_VERSION = 1
|
20
|
+
PLATFORM = 'server'
|
21
|
+
SEED_VALUE = 1
|
22
|
+
MAX_TRAFFIC_PERCENT = 100
|
23
|
+
MAX_TRAFFIC_VALUE = 10_000
|
24
|
+
STATUS_RUNNING = 'RUNNING'
|
25
|
+
LIBRARY_PATH = File.expand_path('../..', __FILE__)
|
26
|
+
HTTP_PROTOCOL = 'http://'
|
27
|
+
HTTPS_PROTOCOL = 'https://'
|
28
|
+
URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'
|
29
|
+
SDK_VERSION = '1.3.0'
|
30
|
+
SDK_NAME = 'ruby'
|
31
|
+
|
32
|
+
module ENDPOINTS
|
33
|
+
BASE_URL = 'dev.visualwebsiteoptimizer.com'
|
34
|
+
ACCOUNT_SETTINGS = '/server-side/settings'
|
35
|
+
TRACK_USER = '/server-side/track-user'
|
36
|
+
TRACK_GOAL = '/server-side/track-goal'
|
37
|
+
end
|
38
|
+
|
39
|
+
module EVENTS
|
40
|
+
TRACK_USER = 'track-user'
|
41
|
+
TRACK_GOAL = 'track-goal'
|
42
|
+
end
|
43
|
+
|
44
|
+
module DATATYPE
|
45
|
+
NUMBER = 'number'
|
46
|
+
STRING = 'string'
|
47
|
+
FUNCTION = 'function'
|
48
|
+
BOOLEAN = 'boolean'
|
49
|
+
end
|
50
|
+
|
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
|
59
|
+
REVENUE = 'REVENUE_TRACKING'
|
60
|
+
CUSTOM = 'CUSTOM_GOAL'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# Copyright 2019 Wingify Software Pvt. Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
require 'murmurhash3'
|
18
|
+
require_relative '../logger'
|
19
|
+
require_relative '../enums'
|
20
|
+
require_relative '../utils/validations'
|
21
|
+
require_relative '../constants'
|
22
|
+
|
23
|
+
class VWO
|
24
|
+
module Core
|
25
|
+
class Bucketer
|
26
|
+
include VWO::Enums
|
27
|
+
include VWO::CONSTANTS
|
28
|
+
include VWO::Utils::Validations
|
29
|
+
|
30
|
+
# Took reference from StackOverflow(https://stackoverflow.com/) to:
|
31
|
+
# convert signed to unsigned integer in python from StackOverflow
|
32
|
+
# Author - Duncan (https://stackoverflow.com/users/107660/duncan)
|
33
|
+
# Source - https://stackoverflow.com/a/20766900/2494535
|
34
|
+
U_MAX_32_BIT = 0xFFFFFFFF
|
35
|
+
MAX_HASH_VALUE = 2**32
|
36
|
+
FILE = FileNameEnum::Bucketer
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@logger = VWO::Logger.get_instance
|
40
|
+
end
|
41
|
+
|
42
|
+
# Calculate if this user should become part of the campaign or not
|
43
|
+
# @param[String] :user_id The unique ID assigned to a user
|
44
|
+
# @param[Dict] :campaign For getting traffic allotted to the campaign
|
45
|
+
# @return[Boolean] If User is a part of Campaign or not
|
46
|
+
#
|
47
|
+
def user_part_of_campaign?(user_id, campaign)
|
48
|
+
unless valid_value?(user_id)
|
49
|
+
@logger.log(
|
50
|
+
LogLevelEnum::ERROR,
|
51
|
+
format(LogMessageEnum::ErrorMessages::INVALID_USER_ID, file: FILE, user_id: user_id, method: 'is_user_part_of_campaign')
|
52
|
+
)
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
if campaign.nil?
|
57
|
+
@logger.log(
|
58
|
+
LogLevelEnum::ERROR,
|
59
|
+
format(LogMessageEnum::ErrorMessages::INVALID_CAMPAIGN, file: FILE, method: 'is_user_part_of_campaign')
|
60
|
+
)
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
|
64
|
+
traffic_allocation = campaign['percentTraffic']
|
65
|
+
|
66
|
+
value_assigned_to_user = get_bucket_value_for_user(user_id)
|
67
|
+
is_user_part = (value_assigned_to_user != 0) && value_assigned_to_user <= traffic_allocation
|
68
|
+
@logger.log(
|
69
|
+
LogLevelEnum::INFO,
|
70
|
+
format(LogMessageEnum::InfoMessages::USER_ELIGIBILITY_FOR_CAMPAIGN, file: FILE, user_id: user_id, is_user_part: is_user_part)
|
71
|
+
)
|
72
|
+
is_user_part
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validates the User ID and
|
76
|
+
# Generates Variation into which the User is bucketed to
|
77
|
+
#
|
78
|
+
# @param[String] :user_id The unique ID assigned to User
|
79
|
+
# @param[Hash] :campaign The Campaign of which User is a part of
|
80
|
+
#
|
81
|
+
# @return[Hash|nil} Variation data into which user is bucketed to
|
82
|
+
# or nil if not
|
83
|
+
def bucket_user_to_variation(user_id, campaign)
|
84
|
+
unless valid_value?(user_id)
|
85
|
+
@logger.log(
|
86
|
+
LogLevelEnum::ERROR,
|
87
|
+
format(LogMessageEnum::ErrorMessages::INVALID_USER_ID, file: FILE, user_id: user_id, method: 'bucket_user_to_variation')
|
88
|
+
)
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
unless campaign
|
93
|
+
@logger.log(
|
94
|
+
LogLevelEnum::ERROR,
|
95
|
+
format(LogMessageEnum::ErrorMessages::INVALID_CAMPAIGN, file: FILE, method: 'is_user_part_of_campaign')
|
96
|
+
)
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
hash_value = MurmurHash3::V32.str_hash(user_id, SEED_VALUE) & U_MAX_32_BIT
|
101
|
+
normalize = MAX_TRAFFIC_VALUE / campaign['percentTraffic']
|
102
|
+
multiplier = normalize / 100
|
103
|
+
bucket_value = generate_bucket_value(
|
104
|
+
hash_value,
|
105
|
+
MAX_TRAFFIC_VALUE,
|
106
|
+
multiplier
|
107
|
+
)
|
108
|
+
|
109
|
+
@logger.log(
|
110
|
+
LogLevelEnum::DEBUG,
|
111
|
+
format(
|
112
|
+
LogMessageEnum::DebugMessages::VARIATION_HASH_BUCKET_VALUE,
|
113
|
+
file: FILE,
|
114
|
+
user_id: user_id,
|
115
|
+
campaign_key: campaign['key'],
|
116
|
+
percent_traffic: campaign['percentTraffic'],
|
117
|
+
bucket_value: bucket_value,
|
118
|
+
hash_value: hash_value
|
119
|
+
)
|
120
|
+
)
|
121
|
+
get_variation(campaign, bucket_value)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# Returns the Variation by checking the Start and End
|
127
|
+
# Bucket Allocations of each Variation
|
128
|
+
#
|
129
|
+
# @param[Hash] :campaign Which contains the variations
|
130
|
+
# @param[Integer] :bucket_value The bucket Value of the user
|
131
|
+
# @return[Hash|nil] Variation data allotted to the user or None if not
|
132
|
+
#
|
133
|
+
def get_variation(campaign, bucket_value)
|
134
|
+
campaign['variations'].find do |variation|
|
135
|
+
(variation['start_variation_allocation']..variation['end_variation_allocation']).cover?(bucket_value)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Validates the User ID and generates Bucket Value of the
|
140
|
+
# User by hashing the userId by murmurHash and scaling it down.
|
141
|
+
#
|
142
|
+
# @param[String] :user_id The unique ID assigned to User
|
143
|
+
# @return[Integer] The bucket Value allotted to User
|
144
|
+
# (between 1 to $this->$MAX_TRAFFIC_PERCENT)
|
145
|
+
def get_bucket_value_for_user(user_id)
|
146
|
+
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)
|
148
|
+
|
149
|
+
@logger.log(
|
150
|
+
LogLevelEnum::DEBUG,
|
151
|
+
format(
|
152
|
+
LogMessageEnum::DebugMessages::USER_HASH_BUCKET_VALUE,
|
153
|
+
file: FILE,
|
154
|
+
hash_value: hash_value,
|
155
|
+
bucket_value: bucket_value,
|
156
|
+
user_id: user_id
|
157
|
+
)
|
158
|
+
)
|
159
|
+
bucket_value
|
160
|
+
end
|
161
|
+
|
162
|
+
# Generates Bucket Value of the User by hashing the User ID by murmurHash
|
163
|
+
# And scaling it down.
|
164
|
+
#
|
165
|
+
# @param[Integer] :hash_value HashValue generated after hashing
|
166
|
+
# @param[Integer] :max_value The value up-to which hashValue needs to be scaled
|
167
|
+
# @param[Integer] :multiplier
|
168
|
+
# @return[Integer] Bucket Value of the User
|
169
|
+
#
|
170
|
+
def generate_bucket_value(hash_value, max_value, multiplier = 1)
|
171
|
+
ratio = hash_value.to_f / MAX_HASH_VALUE
|
172
|
+
multiplied_value = (max_value * ratio + 1) * multiplier
|
173
|
+
multiplied_value.to_i
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|