vwo-sdk 1.3.0
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.
- 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
|