vwo-sdk 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,308 @@
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 '../logger'
18
+ require_relative '../enums'
19
+ require_relative '../utils/campaign'
20
+ require_relative '../utils/validations'
21
+ require_relative 'bucketer'
22
+
23
+ class VWO
24
+ module Core
25
+ class VariationDecider
26
+ attr_reader :user_storage_service
27
+
28
+ include VWO::Enums
29
+ include VWO::Utils::Campaign
30
+ include VWO::Utils::Validations
31
+
32
+ FILE = FileNameEnum::VariationDecider
33
+
34
+ # Initializes various services
35
+ # @param[Hash] - Settings file
36
+ # @param[Class] - Class instance having the capability of
37
+ # get and save.
38
+ def initialize(settings_file, user_storage_service = nil)
39
+ @logger = VWO::Logger.get_instance
40
+ @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
+ @bucketer = VWO::Core::Bucketer.new
44
+ @settings_file = settings_file
45
+ end
46
+
47
+ # Returns variation for the user for the passed campaign-key
48
+ # Check in User Storage, if user found, validate variation and return
49
+ # Otherwise, proceed with variation assignment logic
50
+ #
51
+ #
52
+ # @param[String] :user_id The unique ID assigned to User
53
+ # @param[Hash] :campaign Campaign hash itslef
54
+ # @return[String,String] ({variation_id, variation_name}|Nil): Tuple of
55
+ # variation_id and variation_name if variation allotted, else nil
56
+
57
+ def get_variation(user_id, campaign)
58
+ if campaign
59
+ campaign_key = campaign['key']
60
+ end
61
+
62
+ user_campaign_map = get_user_storage(user_id, campaign_key)
63
+ variation = get_stored_variation(user_id, campaign_key, user_campaign_map) if valid_hash?(user_campaign_map)
64
+
65
+ if variation
66
+ @logger.log(
67
+ LogLevelEnum::INFO,
68
+ format(
69
+ LogMessageEnum::InfoMessages::GOT_STORED_VARIATION,
70
+ file: FILE,
71
+ campaign_key: campaign_key,
72
+ user_id: user_id,
73
+ variation_name: variation['name']
74
+ )
75
+ )
76
+ return variation['id'], variation['name']
77
+ end
78
+
79
+ variation_id, variation_name = get_variation_allotted(user_id, campaign)
80
+
81
+ if variation_name
82
+ save_user_storage(user_id, campaign_key, variation_name) if variation_name
83
+
84
+ @logger.log(
85
+ LogLevelEnum::INFO,
86
+ format(
87
+ LogMessageEnum::InfoMessages::VARIATION_ALLOCATED,
88
+ file: FILE,
89
+ campaign_key: campaign_key,
90
+ user_id: user_id,
91
+ variation_name: variation_name
92
+ )
93
+ )
94
+ else
95
+ @logger.log(
96
+ LogLevelEnum::INFO,
97
+ format(LogMessageEnum::InfoMessages::NO_VARIATION_ALLOCATED, file: FILE, campaign_key: campaign_key, user_id: user_id)
98
+ )
99
+ end
100
+ [variation_id, variation_name]
101
+ end
102
+
103
+ # Returns the Variation Alloted for required campaign
104
+ #
105
+ # @param[String] :user_id The unique ID assigned to User
106
+ # @param[Hash] :campaign Campaign hash for Unique campaign key
107
+ #
108
+ # @return[Hash]
109
+
110
+ def get_variation_allotted(user_id, campaign)
111
+ variation_id, variation_name = nil
112
+ unless valid_value?(user_id)
113
+ @logger.log(
114
+ LogLevelEnum::ERROR,
115
+ format(LogMessageEnum::ErrorMessages::INVALID_USER_ID, file: FILE, user_id: user_id, method: 'get_variation_alloted')
116
+ )
117
+ return variation_id, variation_name
118
+ end
119
+
120
+ if @bucketer.user_part_of_campaign?(user_id, campaign)
121
+ variation_id, variation_name = get_variation_of_campaign_for_user(user_id, campaign)
122
+ @logger.log(
123
+ LogLevelEnum::DEBUG,
124
+ format(
125
+ LogMessageEnum::DebugMessages::GOT_VARIATION_FOR_USER,
126
+ file: FILE,
127
+ variation_name: variation_name,
128
+ user_id: user_id,
129
+ campaign_key: campaign['key'],
130
+ method: 'get_variation_allotted'
131
+ )
132
+ )
133
+ else
134
+ # not part of campaign
135
+ @logger.log(
136
+ LogLevelEnum::DEBUG,
137
+ format(
138
+ LogMessageEnum::DebugMessages::USER_NOT_PART_OF_CAMPAIGN,
139
+ file: FILE,
140
+ user_id: user_id,
141
+ campaign_key: nil,
142
+ method: 'get_variation_allotted'
143
+ )
144
+ )
145
+ end
146
+ [variation_id, variation_name]
147
+ end
148
+
149
+ # Assigns variation to a particular user depending on the campaign PercentTraffic.
150
+ #
151
+ # @param[String] :user_id The unique ID assigned to a user
152
+ # @param[Hash] :campaign The Campaign of which user is to be made a part of
153
+ # @return[Hash] Variation allotted to User
154
+ def get_variation_of_campaign_for_user(user_id, campaign)
155
+ unless campaign
156
+ @logger.log(
157
+ LogLevelEnum::ERROR,
158
+ format(
159
+ LogMessageEnum::ErrorMessages::INVALID_CAMPAIGN,
160
+ file: FILE,
161
+ method: 'get_variation_of_campaign_for_user'
162
+ )
163
+ )
164
+ return nil, nil
165
+ end
166
+
167
+ variation = @bucketer.bucket_user_to_variation(user_id, campaign)
168
+
169
+ if variation && variation['name']
170
+ @logger.log(
171
+ LogLevelEnum::INFO,
172
+ format(
173
+ LogMessageEnum::InfoMessages::GOT_VARIATION_FOR_USER,
174
+ file: FILE,
175
+ variation_name: variation['name'],
176
+ user_id: user_id,
177
+ campaign_key: campaign['key']
178
+ )
179
+ )
180
+ return variation['id'], variation['name']
181
+ end
182
+
183
+ @logger.log(
184
+ LogLevelEnum::INFO,
185
+ format(
186
+ LogMessageEnum::InfoMessages::USER_GOT_NO_VARIATION,
187
+ file: FILE,
188
+ user_id: user_id,
189
+ campaign_key: campaign['key']
190
+ )
191
+ )
192
+ [nil, nil]
193
+ end
194
+
195
+ private
196
+
197
+ # Get the UserStorageData after looking up into get method
198
+ # Being provided via UserStorageService
199
+ #
200
+ # @param[String]: Unique user identifier
201
+ # @param[String]: Unique campaign key
202
+ # @return[Hash|Boolean]: user_storage data
203
+
204
+ def get_user_storage(user_id, campaign_key)
205
+ unless @user_storage_service
206
+ @logger.log(
207
+ LogLevelEnum::DEBUG,
208
+ format(LogMessageEnum::DebugMessages::NO_USER_STORAGE_SERVICE_LOOKUP, file: FILE)
209
+ )
210
+ return false
211
+ end
212
+
213
+ data = @user_storage_service.get(user_id, campaign_key)
214
+ @logger.log(
215
+ LogLevelEnum::INFO,
216
+ format(
217
+ LogMessageEnum::InfoMessages::LOOKING_UP_USER_STORAGE_SERVICE,
218
+ file: FILE,
219
+ user_id: user_id,
220
+ status: data.nil? ? 'Not Found' : 'Found'
221
+ )
222
+ )
223
+ data
224
+ rescue StandardError
225
+ @logger.log(
226
+ LogLevelEnum::ERROR,
227
+ format(LogMessageEnum::ErrorMessages::LOOK_UP_USER_STORAGE_SERVICE_FAILED, file: FILE, user_id: user_id)
228
+ )
229
+ false
230
+ end
231
+
232
+ # If UserStorageService is provided and variation was stored,
233
+ # Get the stored variation
234
+ # @param[String] :user_id
235
+ # @param[String] :campaign_key campaign identified
236
+ # @param[Hash] :user_campaign_map BucketMap consisting of stored user variation
237
+ #
238
+ # @return[Object, nil] if found then variation settings object otherwise None
239
+
240
+ 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
259
+
260
+ @logger.log(
261
+ LogLevelEnum::DEBUG,
262
+ format(
263
+ LogMessageEnum::DebugMessages::NO_STORED_VARIATION,
264
+ file: FILE,
265
+ campaign_key: campaign_key,
266
+ user_id: user_id
267
+ )
268
+ )
269
+ nil
270
+ end
271
+
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)
303
+ )
304
+ false
305
+ end
306
+ end
307
+ end
308
+ end
data/lib/vwo/enums.rb ADDED
@@ -0,0 +1,115 @@
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
+ # rubocop:disable Metrics/LineLength
18
+
19
+ require 'logger'
20
+
21
+ class VWO
22
+ module Enums
23
+ module FileNameEnum
24
+ VWO_PATH = 'vwo'
25
+ UTIL_PATH = 'vwo/utils'
26
+
27
+ VWO = VWO_PATH + '/vwo'
28
+ Bucketer = VWO_PATH + '/core/bucketer'
29
+ VariationDecider = VWO_PATH + '/core/variation_decider'
30
+ EventDispatcher = VWO_PATH + '/services/event_dispatcher'
31
+ Logger = VWO_PATH + '/logger'
32
+ SettingsFileProcessor = VWO_PATH + '/services/settings_file_processor'
33
+
34
+ CampaignUtil = UTIL_PATH + '/campaign'
35
+ FunctionUtil = UTIL_PATH + '/function'
36
+ ImpressionUtil = UTIL_PATH + '/impression'
37
+ UuidUtil = UTIL_PATH + '/uuid'
38
+ ValidateUtil = UTIL_PATH + '/validations'
39
+ end
40
+
41
+ # Logging Enums
42
+ module LogMessageEnum
43
+ # Debug Messages
44
+ module DebugMessages
45
+ LOG_LEVEL_SET = '(%<file>s): Log level set to %<level>s'
46
+ SET_DEVELOPMENT_MODE = '(%<file>s): DEVELOPMENT mode is ON'
47
+ VALID_CONFIGURATION = '(%<file>s): SDK configuration and account settings are valid.'
48
+ CUSTOM_LOGGER_USED = '(%<file>s): Custom logger used'
49
+ SDK_INITIALIZED = '(%<file>s): SDK properly initialized'
50
+ SETTINGS_FILE_PROCESSED = '(%<file>s): Settings file processed'
51
+ NO_STORED_VARIATION = '(%<file>s): No stored variation for UserId:%<user_id>s for Campaign:%<campaign_key>s found in UserStorageService'
52
+ NO_USER_STORAGE_SERVICE_LOOKUP = '(%<file>s): No UserStorageService to look for stored data'
53
+ NO_USER_STORAGE_SERVICE_SAVE = '(%<file>s): No UserStorageService to save data'
54
+ GETTING_STORED_VARIATION = '(%<file>s): Got stored variation for UserId:%<user_id>s of Campaign:%<campaign_key>s as Variation: %<variation_name>s found in UserStorageService'
55
+ CHECK_USER_ELIGIBILITY_FOR_CAMPAIGN = '(%<file>s): campaign:%<campaign_key>s having traffic allocation:%<traffic_allocation>s assigned value:%<traffic_allocation>s to userId:%<user_id>s'
56
+ USER_HASH_BUCKET_VALUE = '(%<file>s): userId:%<user_id>s having hash:%<hash_value>s got bucketValue:%<bucket_value>s'
57
+ VARIATION_HASH_BUCKET_VALUE = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s having percent traffic:%<percent_traffic>s got hash-value:%<hash_value>s and bucket value:%<bucket_value>s'
58
+ IMPRESSION_FAILED = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s got variationName:%<variation_name>s inside method:%<method>s'
59
+ USER_NOT_PART_OF_CAMPAIGN = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s did not become part of campaign method:%<method>s'
60
+ UUID_FOR_USER = '(%<file>s): Uuid generated for userId:%<user_id>s and accountId:%<account_id>s is %<desired_uuid>s'
61
+ IMPRESSION_FOR_TRACK_USER = '(%<file>s): Impression built for track-user - %<properties>s'
62
+ IMPRESSION_FOR_TRACK_GOAL = '(%<file>s): Impression built for track-goal - %<properties>s'
63
+ GOT_VARIATION_FOR_USER = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s got variationName:%<variation_name>s'
64
+ end
65
+
66
+ # Info Messages
67
+ module InfoMessages
68
+ VARIATION_RANGE_ALLOCATION = '(%<file>s): Campaign:%<campaign_key>s having variations:%<variation_name>s with weight:%<variation_weight>s got range as: ( %<start>s - %<end>s ))'
69
+ VARIATION_ALLOCATED = '(%<file>s): UserId:%<user_id>s of Campaign:%<campaign_key>s got variation: %<variation_name>s'
70
+ LOOKING_UP_USER_STORAGE_SERVICE = '(%<file>s): Looked into UserStorageService for userId:%<user_id>s %<status>s'
71
+ SAVING_DATA_USER_STORAGE_SERVICE = '(%<file>s): Saving into UserStorageService for userId:%<user_id>s successful'
72
+ GOT_STORED_VARIATION = '(%<file>s): Got stored variation:%<variation_name>s of campaign:%<campaign_key>s for userId:%<user_id>s from UserStorageService'
73
+ NO_VARIATION_ALLOCATED = '(%<file>s): UserId:%<user_id>s of Campaign:%<campaign_key>s did not get any variation'
74
+ USER_ELIGIBILITY_FOR_CAMPAIGN = '(%<file>s): Is userId:%<user_id>s part of campaign? %<is_user_part>s'
75
+ AUDIENCE_CONDITION_NOT_MET = '(%<file>s): userId:%<user_id>s does not become part of campaign because of not meeting audience conditions'
76
+ GOT_VARIATION_FOR_USER = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s got variationName:%<variation_name>s'
77
+ USER_GOT_NO_VARIATION = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s did not allot any variation'
78
+ IMPRESSION_SUCCESS = '(%<file>s): Impression event - %<end_point>s was successfully received by VWO having main keys: accountId:%<account_id>s userId:%<user_id>s campaignId:%<campaign_id>s and variationId:%<variation_id>s'
79
+ INVALID_VARIATION_KEY = '(%<file>s): Variation was not assigned to userId:%<user_id>s for campaign:%<campaign_key>s'
80
+ end
81
+
82
+ # Warning Messages
83
+ module WarningMessages; end
84
+
85
+ # Error Messages
86
+ module ErrorMessages
87
+ SETTINGS_FILE_CORRUPTED = '(%<file>s): Settings file is corrupted. Please contact VWO Support for help.'
88
+ ACTIVATE_API_MISSING_PARAMS = '(%<file>s): "activate" API got bad parameters. It expects campaignTestKey(String) as first and userId(String) as second argument'
89
+ ACTIVATE_API_CONFIG_CORRUPTED = '(%<file>s): "activate" API has corrupted configuration'
90
+ GET_VARIATION_NAME_API_MISSING_PARAMS = '(%<file>s): "getVariation" API got bad parameters. It expects campaignTestKey(String) as first and userId(String) as second argument'
91
+ GET_VARIATION_API_CONFIG_CORRUPTED = '(%<file>s): "getVariation" API has corrupted configuration'
92
+ TRACK_API_MISSING_PARAMS = '(%<file>s): "track" API got bad parameters. It expects campaignTestKey(String) as first userId(String) as second and goalIdentifier(String/Number) as third argument. Fourth is revenueValue(Float/Number/String) and is required for revenue goal only.'
93
+ TRACK_API_CONFIG_CORRUPTED = '(%<file>s): "track" API has corrupted configuration'
94
+ TRACK_API_GOAL_NOT_FOUND = '(%<file>s): Goal:%<goal_identifier>s not found for campaign:%<campaign_key>s and userId:%<user_id>s'
95
+ TRACK_API_REVENUE_NOT_PASSED_FOR_REVENUE_GOAL = '(%<file>s): Revenue value should be passed for revenue goal:%<goal_identifier>s for campaign:%<campaign_key>s and userId:%<user_id>s'
96
+ TRACK_API_VARIATION_NOT_FOUND = '(%<file>s): Variation not found for campaign:%<campaign_key>s and userId:%<user_id>s'
97
+ CAMPAIGN_NOT_RUNNING = '(%<file>s): API used:%<api>s - Campaign:%<campaign_key>s is not RUNNING. Please verify from VWO App'
98
+ LOOK_UP_USER_STORAGE_SERVICE_FAILED = '(%<file>s): Looking data from UserStorageService failed for userId:%<user_id>s'
99
+ SAVE_USER_STORAGE_SERVICE_FAILED = '(%<file>s): Saving data into UserStorageService failed for userId:%<user_id>s'
100
+ INVALID_CAMPAIGN = '(%<file>s): Invalid campaign passed to %<method>s of this file'
101
+ INVALID_USER_ID = '(%<file>s): Invalid userId:%<user_id>s passed to %<method>s of this file'
102
+ IMPRESSION_FAILED = '(%<file>s): Impression event could not be sent to VWO - %<end_point>s'
103
+ CUSTOM_LOGGER_MISCONFIGURED = '(%<file>s): Custom logger is provided but seems to have mis-configured. %<extra_info>s Please check the API Docs. Using default logger.'
104
+ end
105
+ end
106
+
107
+ module LogLevelEnum
108
+ INFO = ::Logger::INFO
109
+ DEBUG = ::Logger::DEBUG
110
+ WARNING = ::Logger::WARN
111
+ ERROR = ::Logger::ERROR
112
+ end
113
+ end
114
+ end
115
+ # rubocop:enable Metrics/LineLength
data/lib/vwo/logger.rb ADDED
@@ -0,0 +1,37 @@
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 'logger'
18
+
19
+ class VWO
20
+ class Logger
21
+ @logger = nil
22
+ @logger_instance = nil
23
+
24
+ def self.get_instance(logger_instance = nil)
25
+ @@logger ||= VWO::Logger.new(logger_instance)
26
+ end
27
+
28
+ def initialize(logger_instance)
29
+ @@logger_instance = logger_instance || ::Logger.new(STDOUT)
30
+ end
31
+
32
+ # Override this method to handle logs in a custom manner
33
+ def log(level, message)
34
+ @@logger_instance.log(level, message)
35
+ end
36
+ end
37
+ end