kameleoon-client-ruby 2.3.0 → 3.0.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 +4 -4
- data/lib/kameleoon/client_readiness.rb +40 -0
- data/lib/kameleoon/configuration/data_file.rb +41 -0
- data/lib/kameleoon/configuration/feature_flag.rb +3 -2
- data/lib/kameleoon/configuration/rule.rb +18 -3
- data/lib/kameleoon/configuration/settings.rb +3 -1
- data/lib/kameleoon/configuration/variable.rb +0 -2
- data/lib/kameleoon/configuration/variation_exposition.rb +0 -2
- data/lib/kameleoon/data/browser.rb +1 -1
- data/lib/kameleoon/data/conversion.rb +1 -1
- data/lib/kameleoon/data/custom_data.rb +10 -14
- data/lib/kameleoon/data/data.rb +22 -3
- data/lib/kameleoon/data/device.rb +2 -1
- data/lib/kameleoon/data/manager/assigned_variation.rb +38 -0
- data/lib/kameleoon/data/manager/data_array_storage.rb +43 -0
- data/lib/kameleoon/data/manager/data_map_storage.rb +43 -0
- data/lib/kameleoon/data/manager/page_view_visit.rb +19 -0
- data/lib/kameleoon/data/manager/visitor.rb +142 -0
- data/lib/kameleoon/data/manager/visitor_manager.rb +71 -0
- data/lib/kameleoon/data/page_view.rb +2 -1
- data/lib/kameleoon/exceptions.rb +30 -35
- data/lib/kameleoon/hybrid/manager.rb +13 -31
- data/lib/kameleoon/{client.rb → kameleoon_client.rb} +164 -336
- data/lib/kameleoon/kameleoon_client_config.rb +91 -0
- data/lib/kameleoon/kameleoon_client_factory.rb +42 -0
- data/lib/kameleoon/network/activity_event.rb +6 -3
- data/lib/kameleoon/network/cookie/cookie_manager.rb +84 -0
- data/lib/kameleoon/network/net_provider.rb +5 -37
- data/lib/kameleoon/network/network_manager.rb +8 -7
- data/lib/kameleoon/network/request.rb +3 -2
- data/lib/kameleoon/network/response.rb +0 -8
- data/lib/kameleoon/network/url_provider.rb +5 -3
- data/lib/kameleoon/targeting/conditions/browser_condition.rb +2 -3
- data/lib/kameleoon/targeting/conditions/conversion_condition.rb +12 -4
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +19 -13
- data/lib/kameleoon/targeting/conditions/device_condition.rb +3 -4
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +2 -1
- data/lib/kameleoon/targeting/conditions/page_title_condition.rb +11 -4
- data/lib/kameleoon/targeting/conditions/page_url_condition.rb +18 -4
- data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -0
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +11 -6
- data/lib/kameleoon/utils.rb +41 -4
- data/lib/kameleoon/version.rb +1 -1
- data/lib/kameleoon.rb +4 -2
- metadata +14 -10
- data/lib/kameleoon/client_config.rb +0 -44
- data/lib/kameleoon/configuration/experiment.rb +0 -42
- data/lib/kameleoon/cookie.rb +0 -88
- data/lib/kameleoon/factory.rb +0 -43
- data/lib/kameleoon/network/experiment_event.rb +0 -35
- data/lib/kameleoon/storage/variation_storage.rb +0 -42
- 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/
|
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
|
34
|
-
include Cookie
|
31
|
+
class KameleoonClient
|
35
32
|
include Exception
|
36
33
|
|
34
|
+
attr_reader :site_code
|
35
|
+
|
37
36
|
##
|
38
|
-
# You should create
|
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
|
-
|
43
|
+
@config = config
|
43
44
|
@real_time_configuration_service = nil
|
44
45
|
@update_configuration_handler = nil
|
45
46
|
@fetch_configuration_update_job = nil
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
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
|
-
|
60
|
-
|
61
|
-
Network::UrlProvider.new(
|
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
|
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,
|
93
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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::
|
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
|
-
|
164
|
-
|
165
|
-
|
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::
|
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
|
-
|
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::
|
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
|
-
|
209
|
-
if
|
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
|
-
|
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::
|
288
|
-
# @raise [Kameleoon::Exception::
|
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::
|
308
|
-
# @raise [Kameleoon::Exception::
|
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::
|
216
|
+
# @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
|
325
217
|
# @raise [Kameleoon::Exception::FeatureVariableNotFound]
|
326
|
-
# @raise [Kameleoon::Exception::
|
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::
|
350
|
-
# @raise [Kameleoon::Exception::
|
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
|
353
|
-
feature_flag =
|
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::
|
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.
|
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::
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
526
|
-
|
527
|
-
|
528
|
-
|
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
|
-
|
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
|
-
|
544
|
-
|
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 @
|
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
|
-
@
|
612
|
-
|
613
|
-
|
614
|
-
|
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
|
633
|
-
|
634
|
-
|
635
|
-
|
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?
|
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
|
-
|
696
|
-
|
697
|
-
Kameleoon::Targeting::ConditionType::
|
698
|
-
|
699
|
-
|
700
|
-
condition_data =
|
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 =
|
510
|
+
condition_data = visitor.variations unless visitor.nil?
|
707
511
|
when Kameleoon::Targeting::ConditionType::EXCLUSIVE_EXPERIMENT
|
708
|
-
|
709
|
-
|
710
|
-
|
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
|
-
|
719
|
-
feature_flag =
|
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
|
-
|
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,
|
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 =
|
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 =
|
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,
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
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
|
-
|
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
|
-
|
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
|