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.
@@ -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