kameleoon-client-ruby 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/client_readiness.rb +40 -0
  3. data/lib/kameleoon/configuration/data_file.rb +41 -0
  4. data/lib/kameleoon/configuration/feature_flag.rb +3 -2
  5. data/lib/kameleoon/configuration/rule.rb +18 -3
  6. data/lib/kameleoon/configuration/settings.rb +3 -1
  7. data/lib/kameleoon/configuration/variable.rb +0 -2
  8. data/lib/kameleoon/configuration/variation_exposition.rb +0 -2
  9. data/lib/kameleoon/data/browser.rb +1 -1
  10. data/lib/kameleoon/data/conversion.rb +1 -1
  11. data/lib/kameleoon/data/custom_data.rb +10 -14
  12. data/lib/kameleoon/data/data.rb +22 -3
  13. data/lib/kameleoon/data/device.rb +2 -1
  14. data/lib/kameleoon/data/manager/assigned_variation.rb +38 -0
  15. data/lib/kameleoon/data/manager/data_array_storage.rb +43 -0
  16. data/lib/kameleoon/data/manager/data_map_storage.rb +43 -0
  17. data/lib/kameleoon/data/manager/page_view_visit.rb +19 -0
  18. data/lib/kameleoon/data/manager/visitor.rb +142 -0
  19. data/lib/kameleoon/data/manager/visitor_manager.rb +71 -0
  20. data/lib/kameleoon/data/page_view.rb +2 -1
  21. data/lib/kameleoon/exceptions.rb +30 -35
  22. data/lib/kameleoon/hybrid/manager.rb +13 -31
  23. data/lib/kameleoon/{client.rb → kameleoon_client.rb} +164 -336
  24. data/lib/kameleoon/kameleoon_client_config.rb +91 -0
  25. data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
  26. data/lib/kameleoon/network/activity_event.rb +6 -3
  27. data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
  28. data/lib/kameleoon/network/net_provider.rb +5 -37
  29. data/lib/kameleoon/network/network_manager.rb +8 -7
  30. data/lib/kameleoon/network/request.rb +3 -2
  31. data/lib/kameleoon/network/response.rb +0 -8
  32. data/lib/kameleoon/network/url_provider.rb +5 -3
  33. data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
  34. data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
  35. data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
  36. data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
  37. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
  38. data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
  39. data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
  40. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
  41. data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
  42. data/lib/kameleoon/utils.rb +41 -4
  43. data/lib/kameleoon/version.rb +1 -1
  44. data/lib/kameleoon.rb +4 -2
  45. metadata +14 -10
  46. data/lib/kameleoon/client_config.rb +0 -44
  47. data/lib/kameleoon/configuration/experiment.rb +0 -42
  48. data/lib/kameleoon/cookie.rb +0 -88
  49. data/lib/kameleoon/factory.rb +0 -43
  50. data/lib/kameleoon/network/experiment_event.rb +0 -35
  51. data/lib/kameleoon/storage/variation_storage.rb +0 -42
  52. data/lib/kameleoon/storage/visitor_variation.rb +0 -20
@@ -1,27 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/client_readiness'
3
4
  require 'kameleoon/targeting/models'
4
5
  require 'kameleoon/exceptions'
5
- require 'kameleoon/cookie'
6
6
  require 'kameleoon/data/custom_data'
7
7
  require 'kameleoon/data/user_agent'
8
+ require 'kameleoon/data/manager/assigned_variation'
9
+ require 'kameleoon/data/manager/visitor_manager'
8
10
  require 'kameleoon/configuration/feature_flag'
9
11
  require 'kameleoon/configuration/variation'
10
- require 'kameleoon/configuration/settings'
12
+ require 'kameleoon/configuration/data_file'
11
13
  require 'kameleoon/network/activity_event'
12
- require 'kameleoon/network/experiment_event'
13
14
  require 'kameleoon/network/url_provider'
14
15
  require 'kameleoon/network/network_manager'
16
+ require 'kameleoon/network/cookie/cookie_manager'
15
17
  require 'kameleoon/real_time/real_time_configuration_service'
16
- require 'kameleoon/storage/variation_storage'
17
18
  require 'kameleoon/hybrid/manager'
18
19
  require 'kameleoon/storage/cache_factory'
19
20
  require 'rufus/scheduler'
20
21
  require 'yaml'
21
22
  require 'json'
22
- require 'em-synchrony'
23
- require 'em-synchrony/em-http'
24
- require 'em-synchrony/fiber_iterator'
25
23
  require 'objspace'
26
24
  require 'time'
27
25
  require 'ostruct'
@@ -30,37 +28,37 @@ module Kameleoon
30
28
  ##
31
29
  # Client for Kameleoon
32
30
  #
33
- class Client
34
- include Cookie
31
+ class KameleoonClient
35
32
  include Exception
36
33
 
34
+ attr_reader :site_code
35
+
37
36
  ##
38
- # You should create Client with the Client Factory only.
37
+ # You should create KameleoonClient with the Client Factory only.
39
38
  #
40
39
  def initialize(site_code, config)
40
+ raise Exception::SiteCodeIsEmpty, 'Provided site_sode is empty' if site_code&.empty? != false
41
+
41
42
  @site_code = site_code
42
- read_config(config)
43
+ @config = config
43
44
  @real_time_configuration_service = nil
44
45
  @update_configuration_handler = nil
45
46
  @fetch_configuration_update_job = nil
46
- @settings = Kameleoon::Configuration::Settings.new
47
- @experiments = []
48
- @feature_flags = []
49
- @data = {}
50
- @user_agents = {}
51
- @variation_storage = Kameleoon::Storage::VariationStorage.new
52
- @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(
53
- CACHE_EXPIRATION_TIMEOUT,
54
- CACHE_EXPIRATION_TIMEOUT * 3,
55
- Kameleoon::Storage::CacheFactoryImpl.new,
56
- method(:log)
57
- )
47
+ @data_file = Configuration::DataFile.new(config.environment)
48
+ @visitor_manager = Kameleoon::DataManager::VisitorManager.new(config.session_duration_second)
49
+ @hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(HYBRID_EXPIRATION_TIME, method(:log))
58
50
  @network_manager = Network::NetworkManager.new(
59
- @environment,
60
- @default_timeout,
61
- Network::UrlProvider.new(@site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
51
+ config.environment,
52
+ config.default_timeout_millisecond,
53
+ Network::UrlProvider.new(site_code, Network::UrlProvider::DEFAULT_DATA_API_URL),
62
54
  method(:log)
63
55
  )
56
+ @cookie_manager = Network::Cookie::CookieManager.new(config.top_level_domain)
57
+ @readiness = ClientReadiness.new
58
+ end
59
+
60
+ def wait_init
61
+ @readiness.wait
64
62
  end
65
63
 
66
64
  ##
@@ -72,8 +70,8 @@ module Kameleoon
72
70
  # @note The implementation logic is described here:
73
71
  # First we check if a kameleoonVisitorCode cookie or query parameter associated with the current HTTP request can be
74
72
  # found. If so, we will use this as the visitor identifier. If no cookie / parameter is found in the current
75
- # request, we either randomly generate a new identifier, or use the defaultVisitorCode argument as identifier if it
76
- # is passed. This allows our customers to use their own identifiers as visitor codes, should they wish to.
73
+ # request, we either randomly generate a new identifier, or use the default_visitor_code argument as identifier if
74
+ # it is passed. This allows our customers to use their own identifiers as visitor codes, should they wish to.
77
75
  # This can have the added benefit of matching Kameleoon visitors with their own users without any additional
78
76
  # look-ups in a matching table.
79
77
  # In any case, the server-side (via HTTP header) kameleoonVisitorCode cookie is set with the value. Then this
@@ -89,61 +87,15 @@ module Kameleoon
89
87
  # cookies = {'kameleoonVisitorCode' => '1234asdf4321fdsa'}
90
88
  # visitor_code = get_visitor_code(cookies, 'my-domaine.com')
91
89
  #
92
- def get_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
93
- read_and_write(cookies, top_level_domain, cookies, default_visitor_code)
94
- end
95
-
96
- # DEPRECATED. Please use `get_visitor_code` instead.
97
- def obtain_visitor_code(cookies, top_level_domain, default_visitor_code = nil)
98
- warn '[DEPRECATION] `obtain_visitor_code` is deprecated. Please use `get_visitor_code` instead.'
99
- get_visitor_code(cookies, top_level_domain, default_visitor_code)
90
+ def get_visitor_code(cookies, default_visitor_code = nil)
91
+ @cookie_manager.get_or_add(cookies, default_visitor_code)
100
92
  end
101
93
 
102
- ##
103
- # Trigger an experiment.
104
- #
105
- # If such a visitor_code has never been associated with any variation, the SDK returns a randomly selected variation
106
- # If a user with a given visitor_code is already registered with a variation, it will detect the previously
107
- # registered variation and return the variation_id.
108
- # You have to make sure that proper error handling is set up in your code as shown in the example to the right to
109
- # catch potential exceptions.
110
- #
111
- # @param [String] visitor_code Visitor code
112
- # @param [Integer] experiment_id Id of the experiment you want to trigger.
113
- #
114
- # @return [Integer] Id of the variation
115
- #
116
- # @raise [Kameleoon::Exception::ExperimentConfigurationNotFound] Raise when experiment configuration is not found
117
- # @raise [Kameleoon::Exception::NotAllocated] The visitor triggered the experiment, but did not activate it.
118
- # Usually, this happens because the user has been associated with excluded traffic
119
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
120
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
121
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
122
- #
123
- def trigger_experiment(visitor_code, experiment_id)
124
- check_visitor_code(visitor_code)
125
- experiment = @experiments.find { |exp| exp.id.to_s == experiment_id.to_s }
126
- if experiment.nil?
127
- raise Exception::ExperimentConfigurationNotFound.new(experiment_id),
128
- "Experiment #{experiment_id} is not found"
129
- end
130
- check_site_code_enable(experiment)
131
- targeted = check_targeting(visitor_code, experiment_id, experiment)
132
- if targeted
133
- # saved_variation = get_valid_saved_variation(visitor_code, experiment)
134
- variation_id = calculate_variation_for_experiment(visitor_code, experiment)
135
- save_variation(visitor_code, experiment_id, variation_id)
136
- end
137
- _send_tracking_request(visitor_code, experiment_id, variation_id)
138
- unless targeted
139
- raise Exception::NotTargeted.new(visitor_code),
140
- "Experiment #{experiment_id} is not targeted for visitor #{visitor_code}"
141
- end
142
- if variation_id.nil?
143
- raise Exception::NotAllocated.new(visitor_code),
144
- "Experiment #{experiment_id} is not active for visitor #{visitor_code}"
145
- end
146
- variation_id
94
+ def set_legal_consent(visitor_code, consent, cookies = nil)
95
+ Utils::VisitorCode.validate(visitor_code)
96
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
97
+ visitor.legal_consent = consent
98
+ @cookie_manager.update(visitor_code, consent, cookies)
147
99
  end
148
100
 
149
101
  ##
@@ -157,19 +109,12 @@ module Kameleoon
157
109
  # @param [String] visitor_code Visitor code
158
110
  # @param [...Data] data Data to associate with the visitor code
159
111
  #
160
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
112
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
161
113
  #
162
114
  def add_data(visitor_code, *args)
163
- check_visitor_code(visitor_code)
164
- @data.shift while ObjectSpace.memsize_of(@data) > @data_maximum_size * (2**20)
165
- args.each do |data_element|
166
- if data_element.is_a?(UserAgent)
167
- add_user_agent_data(visitor_code, data_element)
168
- next
169
- end
170
- @data[visitor_code] = [] unless @data.key?(visitor_code)
171
- @data[visitor_code].push(data_element)
172
- end
115
+ Utils::VisitorCode.validate(visitor_code)
116
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
117
+ visitor.add_data(method(:log), *args)
173
118
  end
174
119
 
175
120
  ##
@@ -185,10 +130,10 @@ module Kameleoon
185
130
  # @param [Integer] goal_id Id of the goal
186
131
  # @param [Float] revenue Optional - Revenue of the conversion.
187
132
  #
188
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
133
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
189
134
  #
190
135
  def track_conversion(visitor_code, goal_id, revenue = 0.0)
191
- check_visitor_code(visitor_code)
136
+ Utils::VisitorCode.validate(visitor_code)
192
137
  add_data(visitor_code, Conversion.new(goal_id, revenue))
193
138
  flush(visitor_code)
194
139
  end
@@ -202,74 +147,17 @@ module Kameleoon
202
147
  #
203
148
  # @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
204
149
  #
205
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is not null and empty or longer than 255 chars
150
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is not nil and is empty or longer than 255 chars
206
151
  #
207
152
  def flush(visitor_code = nil)
208
- check_visitor_code(visitor_code) unless visitor_code.nil?
209
- if !visitor_code.nil?
210
- _send_tracking_request(visitor_code)
153
+ Utils::VisitorCode.validate(visitor_code) unless visitor_code.nil?
154
+ if visitor_code.nil?
155
+ @visitor_manager.enumerate { |visitor_code, visitor| _send_tracking_request(visitor_code, visitor, false) }
211
156
  else
212
- @data.select { |_, values| values.any? { |data| !data.sent } }.each_key { |key| flush(key) }
157
+ _send_tracking_request(visitor_code, nil, true)
213
158
  end
214
159
  end
215
160
 
216
- ##
217
- # Obtain variation associated data.
218
- #
219
- # To retrieve JSON data associated with a variation, call the get_variation_associated_data method of our SDK.
220
- # The JSON data usually represents some metadata of the variation, and can be configured on our web application
221
- # interface or via our Automation API.
222
- # This method takes the variationID as a parameter and will return the data as a json string.
223
- # It will throw an exception () if the variation ID is wrong or corresponds to an experiment that is not yet online.
224
- #
225
- # @param [Integer] variation_id
226
- #
227
- # @return [Hash] Hash object of the json object.
228
- #
229
- # @raise [Kameleoon::Exception::VariationNotFound] Raise exception if the variation is not found.
230
- #
231
- def get_variation_associated_data(variation_id)
232
- variation = @experiments.map(&:variations).flatten.select { |var| var['id'].to_i == variation_id.to_i }.first
233
- if variation.nil?
234
- raise Exception::VariationConfigurationNotFound.new(variation_id),
235
- "Variation key #{variation_id} not found"
236
- else
237
- JSON.parse(variation['customJson'])
238
- end
239
- end
240
-
241
- # DEPRECATED. Please use `get_variation_associated_data` instead.
242
- def obtain_variation_associated_data(variation_id)
243
- warn '[DEPRECATION] `obtain_variation_associated_data` is deprecated.
244
- Please use `get_variation_associated_data` instead.'
245
- get_variation_associated_data(variation_id)
246
- end
247
-
248
- # #
249
- # Activate a feature toggle.
250
-
251
- # This method takes a visitor_code and feature_key (or feature_id) as mandatory arguments to check if the specified
252
- # feature will be active for a given user.
253
- # If such a user has never been associated with this feature flag, the SDK returns a boolean value randomly
254
- # (true if the user should have this feature or false if not). If a user with a given visitorCode is already
255
- # registered with this feature flag, it will detect the previous featureFlag value.
256
- # You have to make sure that proper error handling is set up in your code as shown in the example
257
- # to the right to catch potential exceptions.
258
-
259
- # @param [String] visitor_code
260
- # @param [String | Integer] feature_key
261
-
262
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
263
- # @raise [Kameleoon::Exception::NotTargeted] The visitor is not targeted by the experiment, as the
264
- # associated targeting segment conditions were not fulfilled. He should see the reference variation
265
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
266
- #
267
- # DEPRECATED. Please use `feature_active?` instead.
268
- def activate_feature(visitor_code, feature_key)
269
- warn '[DEPRECATION] `activate_feature` is deprecated. Please use `feature_active?` instead.'
270
- feature_active?(visitor_code, feature_key)
271
- end
272
-
273
161
  ##
274
162
  # Check if feature is active for a given visitor code
275
163
  #
@@ -284,12 +172,14 @@ module Kameleoon
284
172
  # @param [String] visitor_code Unique identifier of the user. This field is mandatory.
285
173
  # @param [String] feature_key Key of the feature flag you want to expose to a user. This field is mandatory.
286
174
  #
287
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
288
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
175
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
176
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
289
177
  #
290
178
  def feature_active?(visitor_code, feature_key)
291
179
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
292
180
  variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
181
+ rescue Exception::FeatureEnvironmentDisabled
182
+ false
293
183
  end
294
184
 
295
185
  #
@@ -304,8 +194,10 @@ module Kameleoon
304
194
  # @param [String] visitor_code
305
195
  # @param [String] feature_key
306
196
  #
307
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
308
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
197
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
198
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
199
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
200
+ # the current environment
309
201
  #
310
202
  def get_feature_variation_key(visitor_code, feature_key)
311
203
  _, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -321,9 +213,11 @@ module Kameleoon
321
213
  # @param [String] feature_key
322
214
  # @param [String] variable_name
323
215
  #
324
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
216
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
325
217
  # @raise [Kameleoon::Exception::FeatureVariableNotFound]
326
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
218
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
219
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
220
+ # the current environment
327
221
  #
328
222
  def get_feature_variable(visitor_code, feature_key, variable_name)
329
223
  feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key)
@@ -346,14 +240,15 @@ module Kameleoon
346
240
  # @param [String] feature_key
347
241
  # @param [String] variation_key
348
242
  #
349
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
350
- # @raise [Kameleoon::Exception::VariationConfigurationNotFound]
243
+ # @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
244
+ # @raise [Kameleoon::Exception::FeatureVariationNotFound]
245
+ # @raise [Kameleoon::Exception::FeatureEnvironmentDisabled]
351
246
  #
352
- def get_feature_all_variables(feature_key, variation_key)
353
- feature_flag = find_feature_flag(feature_key)
247
+ def get_feature_variation_variables(feature_key, variation_key)
248
+ feature_flag = @data_file.get_feature_flag(feature_key)
354
249
  variation = feature_flag.get_variation_key(variation_key)
355
250
  if variation.nil?
356
- raise Exception::VariationConfigurationNotFound.new(variation_key),
251
+ raise Exception::FeatureVariationNotFound.new(variation_key),
357
252
  "Variation key #{variation_key} not found"
358
253
  end
359
254
  variables = {}
@@ -361,27 +256,6 @@ module Kameleoon
361
256
  variables
362
257
  end
363
258
 
364
- ##
365
- # Retrieve a feature variable.
366
- #
367
- # A feature variable can be changed easily via our web application.
368
- #
369
- # @param [String | Integer] feature_key
370
- # @param [String] variable_key
371
- #
372
- # @raise [Kameleoon::Exception::FeatureConfigurationNotFound] Feature Flag isn't found in this configuration
373
- # @raise [Kameleoon::Exception::FeatureVariableNotFound]
374
- #
375
- # DEPRECATED. Please use `get_feature_variable` instead.
376
- def obtain_feature_variable(feature_key, variable_key)
377
- warn '[DEPRECATION] `obtain_feature_variable` is deprecated. Please use `get_feature_variable` instead.'
378
- all_variables = get_feature_all_variables(
379
- feature_key,
380
- Configuration::VariationType::VARIATION_OFF
381
- )
382
- all_variables[variable_key]
383
- end
384
-
385
259
  ##
386
260
  # The get_remote_data method allows you to retrieve data (according to a key passed as argument)
387
261
  # stored on a remote Kameleoon server. Usually data will be stored on our remote
@@ -399,13 +273,6 @@ module Kameleoon
399
273
  JSON.parse(response) if response
400
274
  end
401
275
 
402
- ##
403
- # DEPRECATED. Please use `get_feature_variable` instead.
404
- def retrieve_data_from_remote_source(key, timeout = @default_timeout)
405
- warn '[DEPRECATION] `retrieve_data_from_remote_source` is deprecated. Please use `get_remote_date` instead.'
406
- get_remote_data(key, timeout)
407
- end
408
-
409
276
  ##
410
277
  # The get_remote_visitor_data is a method for retrieving custom data for
411
278
  # the latest visit of `visitor_code` from Kameleoon Data API and optionally adding it
@@ -426,55 +293,27 @@ module Kameleoon
426
293
  data_array
427
294
  end
428
295
 
429
- ##
430
- # Returns a list of all experiment ids
431
- #
432
- # @return [Array] array of all experiment ids
433
- def get_experiment_list # rubocop:disable Naming/AccessorMethodName
434
- @experiments.map { |it| it.id.to_i }
435
- end
436
-
437
- ##
438
- # Returns a list of all experiment ids targeted for a visitor
439
- # if only_allocated is `true` returns a list of allocated experiments for a visitor
440
- #
441
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
442
- #
443
- # @return [Array] array of all experiment ids accorging to a only_allocated parameter
444
- def get_experiment_list_for_visitor(visitor_code, only_allocated: true)
445
- list_ids = []
446
- @experiments.each do |experiment|
447
- next unless check_targeting(visitor_code, experiment.id.to_i, experiment)
448
- next if only_allocated && calculate_variation_for_experiment(visitor_code, experiment).nil?
449
-
450
- list_ids.push(experiment.id.to_i)
451
- end
452
- list_ids
453
- end
454
-
455
296
  ##
456
297
  # Returns a list of all feature flag keys
457
298
  #
458
299
  # @return [Array] array of all feature flag keys
459
300
  def get_feature_list # rubocop:disable Naming/AccessorMethodName
460
- @feature_flags.map(&:feature_key)
301
+ @data_file.feature_flags.keys
461
302
  end
462
303
 
463
304
  ##
464
305
  # Returns a list of active feature flag keys for a visitor
465
306
  #
466
- # @raise [Kameleoon::Exception::VisitorCodeNotValid] If the visitor code is empty or longer than 255 chars
307
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
467
308
  #
468
309
  # @return [Array] array of active feature flag keys for a visitor
469
310
  def get_active_feature_list_for_visitor(visitor_code)
470
- check_visitor_code(visitor_code)
311
+ Utils::VisitorCode.validate(visitor_code)
471
312
  list_keys = []
472
- @feature_flags.each do |feature_flag|
313
+ @data_file.feature_flags.each do |feature_key, feature_flag|
473
314
  variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
474
315
  variation_key = _get_variation_key(variation, rule, feature_flag)
475
- if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
476
- list_keys.push(feature_flag.feature_key)
477
- end
316
+ list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
478
317
  end
479
318
  list_keys
480
319
  end
@@ -499,56 +338,49 @@ module Kameleoon
499
338
  # @return [String] JavasScript code to be inserted in your page to send automatically
500
339
  # the exposure events to the analytics solution you are using.
501
340
  def get_engine_tracking_code(visitor_code)
502
- @hybrid_manager.get_engine_tracking_code(visitor_code)
341
+ visitor_variations = @visitor_manager.get_visitor(visitor_code)&.variations
342
+ @hybrid_manager.get_engine_tracking_code(visitor_variations)
503
343
  end
504
344
 
505
345
  private
506
346
 
507
- REFERENCE = 0
508
- CACHE_EXPIRATION_TIMEOUT = 5
509
- attr :site_code, :client_id, :client_secret, :access_token, :experiments, :feature_flags, :scheduler, :data,
510
- :tracking_url, :default_timeout, :interval, :memory_limit, :verbose_mode
511
-
512
- def read_config(config)
513
- @client_id = config.client_id
514
- @client_secret = config.client_secret
515
- @default_timeout = config.default_timeout # in ms
516
- @interval = "#{config.configuration_refresh_interval}m"
517
- @data_maximum_size = config.visitor_data_maximum_size
518
- @environment = config.environment
519
- @verbose_mode = config.verbose_mode
520
- if @client_id.nil? || @client_secret.nil?
521
- warn 'Kameleoon SDK: Credentials are invalid: client_id or client_secret (or both) are empty'
522
- end
523
- end
347
+ HYBRID_EXPIRATION_TIME = 5
524
348
 
525
- def fetch_configuration
526
- Rufus::Scheduler.singleton.in '0s' do
527
- log('Initial configuration fetch is started.')
528
- fetch_configuration_job
349
+ def fetch_configuration_initially
350
+ log('Initial configuration fetch is started.')
351
+ Thread.new do
352
+ ok = false
353
+ begin
354
+ ok = obtain_configuration
355
+ log('Initial configuration fetch failed') unless ok
356
+ rescue StandardError => e
357
+ log("Initial configuration fetch failed: #{e}")
358
+ end
359
+ @readiness.set(ok)
360
+ manage_configuration_update(@data_file.settings.real_time_update) if ok
529
361
  end
530
362
  end
531
363
 
532
364
  def fetch_configuration_job(time_stamp = nil)
533
- EM.synchrony do
365
+ Thread.new do
366
+ ok = false
534
367
  begin
535
368
  ok = obtain_configuration(time_stamp)
536
- if !ok && @settings.real_time_update
537
- @settings.real_time_update = false
538
- log('Switching to polling mode due to failed fetch')
539
- end
540
369
  rescue StandardError => e
541
370
  log("Error occurred during configuration fetching: #{e}")
542
371
  end
543
- manage_configuration_update(@settings.real_time_update)
544
- EM.stop
372
+ if !ok && @data_file.settings.real_time_update
373
+ @data_file.settings.real_time_update = false
374
+ log('Switching to polling mode due to failed fetch')
375
+ end
376
+ manage_configuration_update(@data_file.settings.real_time_update)
545
377
  end
546
378
  end
547
379
 
548
380
  def start_configuration_update_job_if_needed
549
381
  return unless @fetch_configuration_update_job.nil?
550
382
 
551
- @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @interval do
383
+ @fetch_configuration_update_job = Rufus::Scheduler.singleton.schedule_every @config.refresh_interval_second do
552
384
  log('Scheduled job to fetch configuration is started.')
553
385
  fetch_configuration_job
554
386
  end
@@ -608,12 +440,10 @@ module Kameleoon
608
440
  return false unless response
609
441
 
610
442
  configuration = JSON.parse(response)
611
- @experiments = Kameleoon::Configuration::Experiment.create_from_array(configuration['experiments']) ||
612
- @experiments
613
- @feature_flags = Kameleoon::Configuration::FeatureFlag.create_from_array(
614
- configuration['featureFlagConfigurations']
615
- )
616
- @settings.update(configuration['configuration'])
443
+ @data_file = Configuration::DataFile.new(@config.environment).init(configuration)
444
+ @cookie_manager.consent_required =
445
+ @data_file.settings.is_consent_required && !@data_file.has_any_targeted_delivery_rule
446
+
617
447
  call_update_handler_if_needed(!time_stamp.nil?)
618
448
  log "Feature flags are fetched: #{response.inspect}"
619
449
  true
@@ -629,49 +459,16 @@ module Kameleoon
629
459
  @update_configuration_handler.call
630
460
  end
631
461
 
632
- def calculate_variation_for_experiment(visitor_code, experiment)
633
- threshold = obtain_hash_double(visitor_code, experiment.respool_time, experiment.id)
634
- experiment.deviations.each do |key, value|
635
- threshold -= value
636
- return key.to_s.to_i if threshold.negative?
637
- end
638
- nil
639
- end
640
-
641
- def find_feature_flag(feature_key)
642
- if feature_key.is_a?(String)
643
- feature_flag = @feature_flags.select { |ff| ff.feature_key == feature_key }.first
644
- else
645
- raise TypeError.new('Feature key should be a String or an Integer.'),
646
- 'Feature key should be a String or an Integer.'
647
- end
648
- error_message = "Feature #{feature_key} not found"
649
- raise Exception::FeatureConfigurationNotFound.new(feature_key), error_message if feature_flag.nil?
650
-
651
- feature_flag
652
- end
653
-
654
- def check_site_code_enable(campaign)
655
- raise Exception::SiteCodeDisabled.new(site_code), site_code unless campaign.site_enabled
656
- end
657
-
658
- def data_not_sent(visitor_code)
659
- @data.key?(visitor_code) ? @data[visitor_code].reject(&:sent) : []
462
+ def dispose
463
+ stop_configuration_update_job_if_needed
464
+ stop_real_time_configuration_service_if_needed
465
+ @visitor_manager.stop
660
466
  end
661
467
 
662
468
  def log(text)
663
469
  print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
664
470
  end
665
471
 
666
- def add_user_agent_data(visitor_code, user_agent)
667
- @user_agents[visitor_code] = user_agent
668
- end
669
-
670
- def set_user_agent_to_headers(visitor_code, headers)
671
- user_agent = @user_agents[visitor_code]
672
- headers['User-Agent'] = user_agent.value unless user_agent.nil?
673
- end
674
-
675
472
  # Uncomment when using storage
676
473
  # def get_valid_saved_variation(visitor_code, experiment)
677
474
  # variation_id = @variation_storage.get_variation_id(visitor_code, experiment.id.to_i)
@@ -685,29 +482,38 @@ module Kameleoon
685
482
 
686
483
  def check_targeting(visitor_code, campaign_id, exp_ff_rule)
687
484
  segment = exp_ff_rule.targeting_segment
688
- segment.nil? || segment.check_tree(->(type) { get_condition_data(type, visitor_code, campaign_id) })
485
+ return true if segment.nil?
486
+
487
+ visitor = @visitor_manager.get_visitor(visitor_code)
488
+ segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
689
489
  end
690
490
 
691
- def get_condition_data(type, visitor_code, campaign_id)
491
+ def get_condition_data(type, visitor, visitor_code, campaign_id)
692
492
  condition_data = nil
693
493
  case type
694
- when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM,
695
- Kameleoon::Targeting::ConditionType::PAGE_TITLE,
696
- Kameleoon::Targeting::ConditionType::PAGE_URL,
697
- Kameleoon::Targeting::ConditionType::DEVICE_TYPE,
698
- Kameleoon::Targeting::ConditionType::BROWSER,
699
- Kameleoon::Targeting::ConditionType::CONVERSIONS
700
- condition_data = (@data[visitor_code] || []).flatten
494
+ when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM
495
+ condition_data = visitor.custom_data unless visitor.nil?
496
+ when Kameleoon::Targeting::ConditionType::PAGE_TITLE,
497
+ Kameleoon::Targeting::ConditionType::PAGE_URL
498
+ condition_data = visitor.page_view_visits unless visitor.nil?
499
+ when Kameleoon::Targeting::ConditionType::DEVICE_TYPE
500
+ condition_data = visitor.device unless visitor.nil?
501
+ when Kameleoon::Targeting::ConditionType::BROWSER
502
+ condition_data = visitor.browser unless visitor.nil?
503
+ when Kameleoon::Targeting::ConditionType::CONVERSIONS
504
+ condition_data = visitor.conversions unless visitor.nil?
701
505
  when Kameleoon::Targeting::ConditionType::SDK_LANGUAGE
702
506
  condition_data = Kameleoon::Targeting::SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
703
507
  when Kameleoon::Targeting::ConditionType::VISITOR_CODE
704
508
  condition_data = visitor_code
705
509
  when Kameleoon::Targeting::ConditionType::TARGET_EXPERIMENT
706
- condition_data = @variation_storage.get_hash_saved_variation_id(visitor_code)
510
+ condition_data = visitor.variations unless visitor.nil?
707
511
  when Kameleoon::Targeting::ConditionType::EXCLUSIVE_EXPERIMENT
708
- condition_data = OpenStruct.new
709
- condition_data.experiment_id = campaign_id
710
- condition_data.storage = @variation_storage.get_hash_saved_variation_id(visitor_code)
512
+ unless visitor.nil?
513
+ condition_data = OpenStruct.new
514
+ condition_data.experiment_id = campaign_id
515
+ condition_data.storage = visitor.variations
516
+ end
711
517
  end
712
518
  condition_data
713
519
  end
@@ -715,16 +521,21 @@ module Kameleoon
715
521
  ##
716
522
  # helper method for getting variation key for feature flag
717
523
  def _get_feature_variation_key(visitor_code, feature_key)
718
- check_visitor_code(visitor_code)
719
- feature_flag = find_feature_flag(feature_key)
524
+ Utils::VisitorCode.validate(visitor_code)
525
+ feature_flag = @data_file.get_feature_flag(feature_key)
720
526
  variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
721
527
  variation_key = _get_variation_key(variation, rule, feature_flag)
528
+ visitor = nil
722
529
  unless rule.nil?
723
530
  experiment_id = rule.experiment_id
724
531
  variation_id = variation.variation_id unless variation.nil?
725
- save_variation(visitor_code, experiment_id, variation_id)
532
+ visitor = @visitor_manager.get_or_create_visitor(visitor_code)
533
+ unless experiment_id.nil? || variation_id.nil?
534
+ as_variation = Kameleoon::DataManager::AssignedVariation.new(experiment_id, variation_id, rule.type)
535
+ visitor.assign_variation(as_variation)
536
+ end
726
537
  end
727
- _send_tracking_request(visitor_code, experiment_id, variation_id)
538
+ _send_tracking_request(visitor_code, visitor)
728
539
  [feature_flag, variation_key]
729
540
  end
730
541
 
@@ -737,13 +548,13 @@ module Kameleoon
737
548
  next unless check_targeting(visitor_code, feature_flag.id, rule)
738
549
 
739
550
  # uses for rule exposition
740
- hash_rule = obtain_hash_double_rule(visitor_code, rule.id, rule.respool_time)
551
+ hash_rule = Utils::HashDouble.obtain_rule(visitor_code, rule.id, rule.respool_time)
741
552
  # check main expostion for rule with hashRule
742
553
  if hash_rule <= rule.exposition
743
554
  return [rule.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
744
555
 
745
556
  # uses for variation's expositions
746
- hash_variation = obtain_hash_double_rule(visitor_code, rule.experiment_id, rule.respool_time)
557
+ hash_variation = Utils::HashDouble.obtain_rule(visitor_code, rule.experiment_id, rule.respool_time)
747
558
  # get variation key with new hashVariation
748
559
  variation = rule.get_variation(hash_variation)
749
560
  return [variation, rule] unless variation.nil?
@@ -755,16 +566,8 @@ module Kameleoon
755
566
  [nil, nil]
756
567
  end
757
568
 
758
- def save_variation(visitor_code, experiment_id, variation_id)
759
- return if experiment_id.nil? || variation_id.nil?
760
-
761
- @variation_storage.update_variation(visitor_code, experiment_id, variation_id)
762
- @hybrid_manager.add_variation(visitor_code, experiment_id, variation_id)
763
- end
764
-
765
569
  def _get_variation_key(var_by_exp, rule, feature_flag)
766
570
  return var_by_exp.variation_key unless var_by_exp.nil?
767
-
768
571
  return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experimentation_type?
769
572
 
770
573
  feature_flag.default_variation_key
@@ -772,16 +575,37 @@ module Kameleoon
772
575
 
773
576
  ##
774
577
  # helper method for sending tracking requests for new FF
775
- def _send_tracking_request(visitor_code, experiment_id = nil, variation_id = nil)
776
- user_agent = @user_agents[visitor_code]&.value
777
- data_not_sent = data_not_sent(visitor_code)
778
- if experiment_id.nil? || variation_id.nil?
779
- data_not_sent.append(Network::ActivityEvent.new) if data_not_sent.empty?
578
+ def _send_tracking_request(visitor_code, visitor = nil, force_request = true)
579
+ if visitor.nil?
580
+ visitor = @visitor_manager.get_visitor(visitor_code)
581
+ return if visitor.nil? && @data_file.settings.is_consent_required
582
+ end
583
+ consent = consent_provided?(visitor)
584
+ user_agent = visitor&.user_agent
585
+ unsent = visitor.nil? ? [] : select_unsent_data(visitor, consent)
586
+ if unsent.empty?
587
+ return unless force_request && consent
588
+
589
+ unsent.push(Network::ActivityEvent.new)
590
+ end
591
+ log "Start post tracking: #{unsent.inspect}"
592
+ @network_manager.send_tracking_data(visitor_code, unsent, user_agent)
593
+ end
594
+
595
+ def select_unsent_data(visitor, consent)
596
+ unsent = []
597
+ if consent
598
+ visitor.enumerate_sendable_data { |data| unsent.push(data) unless data.sent }
780
599
  else
781
- data_not_sent.append(Network::ExperimentEvent.new(experiment_id, variation_id))
600
+ visitor.conversions.enumerate { |conversion| unsent.push(conversion) unless conversion.sent }
601
+ if @data_file.has_any_targeted_delivery_rule
602
+ visitor.variations.enumerate do |variation|
603
+ unsent.push(variation) unless
604
+ variation.sent || (variation.rule_type != Configuration::RuleType::TARGETED_DELIVERY)
605
+ end
606
+ end
782
607
  end
783
- log "Start post tracking: #{data_not_sent.inspect}"
784
- @network_manager.send_tracking_data(visitor_code, data_not_sent, user_agent)
608
+ unsent
785
609
  end
786
610
 
787
611
  ##
@@ -820,5 +644,9 @@ module Kameleoon
820
644
  log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
821
645
  []
822
646
  end
647
+
648
+ def consent_provided?(visitor)
649
+ !@data_file.settings.is_consent_required || visitor&.legal_consent
650
+ end
823
651
  end
824
652
  end