kameleoon-client-ruby 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kameleoon/configuration/custom_data_info.rb +43 -0
- data/lib/kameleoon/configuration/data_file.rb +31 -2
- data/lib/kameleoon/configuration/rule.rb +4 -1
- data/lib/kameleoon/configuration/variation_exposition.rb +1 -1
- data/lib/kameleoon/data/browser.rb +19 -0
- data/lib/kameleoon/data/cookie.rb +14 -0
- data/lib/kameleoon/data/custom_data.rb +2 -0
- data/lib/kameleoon/data/data.rb +2 -0
- data/lib/kameleoon/data/geolocation.rb +45 -0
- data/lib/kameleoon/data/kcs_heat.rb +12 -0
- data/lib/kameleoon/data/manager/assigned_variation.rb +2 -1
- data/lib/kameleoon/data/manager/page_view_visit.rb +16 -2
- data/lib/kameleoon/data/manager/visitor.rb +71 -14
- data/lib/kameleoon/data/manager/visitor_manager.rb +23 -1
- data/lib/kameleoon/data/operating_system.rb +72 -0
- data/lib/kameleoon/data/page_view.rb +2 -2
- data/lib/kameleoon/data/visitor_visits.rb +20 -0
- data/lib/kameleoon/kameleoon_client.rb +122 -94
- data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +55 -0
- data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +188 -0
- data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +3 -1
- data/lib/kameleoon/network/access_token_source.rb +22 -5
- data/lib/kameleoon/network/network_manager.rb +10 -10
- data/lib/kameleoon/network/response.rb +1 -1
- data/lib/kameleoon/network/url_provider.rb +16 -12
- data/lib/kameleoon/targeting/condition.rb +24 -2
- data/lib/kameleoon/targeting/condition_factory.rb +39 -6
- data/lib/kameleoon/targeting/conditions/cookie_condition.rb +55 -0
- data/lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb +27 -0
- data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +33 -0
- data/lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb +37 -0
- data/lib/kameleoon/targeting/conditions/number_condition.rb +38 -0
- data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +27 -0
- data/lib/kameleoon/targeting/conditions/page_view_number_condition.rb +28 -0
- data/lib/kameleoon/targeting/conditions/previous_page_condition.rb +35 -0
- data/lib/kameleoon/targeting/conditions/segment_condition.rb +42 -0
- data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +59 -0
- data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +29 -0
- data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +28 -0
- data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +23 -0
- data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +34 -0
- data/lib/kameleoon/targeting/targeting_manager.rb +68 -0
- data/lib/kameleoon/types/remote_visitor_data_filter.rb +29 -0
- data/lib/kameleoon/types/variable.rb +17 -0
- data/lib/kameleoon/types/variation.rb +18 -0
- data/lib/kameleoon/utils.rb +1 -0
- data/lib/kameleoon/version.rb +1 -1
- metadata +28 -4
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +0 -18
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +0 -45
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/client_readiness'
|
|
4
4
|
require 'kameleoon/targeting/models'
|
|
5
|
+
require 'kameleoon/targeting/targeting_manager'
|
|
5
6
|
require 'kameleoon/exceptions'
|
|
6
7
|
require 'kameleoon/data/custom_data'
|
|
7
8
|
require 'kameleoon/data/user_agent'
|
|
@@ -19,6 +20,7 @@ require 'kameleoon/managers/warehouse/warehouse_manager'
|
|
|
19
20
|
require 'kameleoon/real_time/real_time_configuration_service'
|
|
20
21
|
require 'kameleoon/hybrid/manager'
|
|
21
22
|
require 'kameleoon/storage/cache_factory'
|
|
23
|
+
require 'kameleoon/managers/remote_data/remote_data_manager'
|
|
22
24
|
require 'rufus/scheduler'
|
|
23
25
|
require 'yaml'
|
|
24
26
|
require 'json'
|
|
@@ -46,8 +48,8 @@ module Kameleoon
|
|
|
46
48
|
@real_time_configuration_service = nil
|
|
47
49
|
@update_configuration_handler = nil
|
|
48
50
|
@fetch_configuration_update_job = nil
|
|
49
|
-
@data_file = Configuration::DataFile.new(config.environment)
|
|
50
|
-
@visitor_manager = Kameleoon::DataManager::VisitorManager.new(config.session_duration_second)
|
|
51
|
+
@data_file = Configuration::DataFile.new(config.environment, method(:log))
|
|
52
|
+
@visitor_manager = Kameleoon::DataManager::VisitorManager.new(config.session_duration_second, method(:log))
|
|
51
53
|
@hybrid_manager = Kameleoon::Hybrid::ManagerImpl.new(HYBRID_EXPIRATION_TIME, method(:log))
|
|
52
54
|
@network_manager = Network::NetworkManager.new(
|
|
53
55
|
config.environment,
|
|
@@ -57,8 +59,12 @@ module Kameleoon
|
|
|
57
59
|
method(:log)
|
|
58
60
|
)
|
|
59
61
|
@warehouse_manager = Managers::Warehouse::WarehouseManager.new(@network_manager, @visitor_manager, method(:log))
|
|
62
|
+
@remote_data_manager = Kameleoon::Managers::RemoteData::RemoteDataManager.new(@network_manager, @visitor_manager,
|
|
63
|
+
method(:log))
|
|
60
64
|
@cookie_manager = Network::Cookie::CookieManager.new(config.top_level_domain)
|
|
61
65
|
@readiness = ClientReadiness.new
|
|
66
|
+
@targeting_manager = Kameleoon::Targeting::TargetingManager.new(@visitor_manager, @data_file)
|
|
67
|
+
@visitor_manager.custom_data_info = @data_file.custom_data_info
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
def wait_init
|
|
@@ -117,8 +123,7 @@ module Kameleoon
|
|
|
117
123
|
#
|
|
118
124
|
def add_data(visitor_code, *args)
|
|
119
125
|
Utils::VisitorCode.validate(visitor_code)
|
|
120
|
-
|
|
121
|
-
visitor.add_data(method(:log), *args)
|
|
126
|
+
@visitor_manager.add_data(visitor_code, *args)
|
|
122
127
|
end
|
|
123
128
|
|
|
124
129
|
##
|
|
@@ -133,13 +138,15 @@ module Kameleoon
|
|
|
133
138
|
# @param [String] visitor_code Visitor code
|
|
134
139
|
# @param [Integer] goal_id Id of the goal
|
|
135
140
|
# @param [Float] revenue Optional - Revenue of the conversion.
|
|
141
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
142
|
+
# This field is optional.
|
|
136
143
|
#
|
|
137
144
|
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
|
|
138
145
|
#
|
|
139
|
-
def track_conversion(visitor_code, goal_id, revenue = 0.0)
|
|
146
|
+
def track_conversion(visitor_code, goal_id, revenue = 0.0, is_unique_identifier: false)
|
|
140
147
|
Utils::VisitorCode.validate(visitor_code)
|
|
141
148
|
add_data(visitor_code, Conversion.new(goal_id, revenue))
|
|
142
|
-
flush(visitor_code)
|
|
149
|
+
flush(visitor_code, is_unique_identifier: is_unique_identifier)
|
|
143
150
|
end
|
|
144
151
|
|
|
145
152
|
##
|
|
@@ -150,15 +157,19 @@ module Kameleoon
|
|
|
150
157
|
# With this method you can manually send it.
|
|
151
158
|
#
|
|
152
159
|
# @param [String] visitor_code Optional field - Visitor code, without visitor code it flush all of the data
|
|
160
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
161
|
+
# This field is optional.
|
|
153
162
|
#
|
|
154
163
|
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is not nil and is empty or longer than 255 chars
|
|
155
164
|
#
|
|
156
|
-
def flush(visitor_code = nil)
|
|
165
|
+
def flush(visitor_code = nil, is_unique_identifier: false)
|
|
157
166
|
Utils::VisitorCode.validate(visitor_code) unless visitor_code.nil?
|
|
158
167
|
if visitor_code.nil?
|
|
159
|
-
@visitor_manager.enumerate
|
|
168
|
+
@visitor_manager.enumerate do |visitor_code, visitor|
|
|
169
|
+
_send_tracking_request(visitor_code, visitor, false, is_unique_identifier)
|
|
170
|
+
end
|
|
160
171
|
else
|
|
161
|
-
_send_tracking_request(visitor_code, nil, true)
|
|
172
|
+
_send_tracking_request(visitor_code, nil, true, is_unique_identifier)
|
|
162
173
|
end
|
|
163
174
|
end
|
|
164
175
|
|
|
@@ -175,12 +186,14 @@ module Kameleoon
|
|
|
175
186
|
#
|
|
176
187
|
# @param [String] visitor_code Unique identifier of the user. This field is mandatory.
|
|
177
188
|
# @param [String] feature_key Key of the feature flag you want to expose to a user. This field is mandatory.
|
|
189
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
190
|
+
# This field is optional.
|
|
178
191
|
#
|
|
179
192
|
# @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
|
|
180
193
|
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
|
|
181
194
|
#
|
|
182
|
-
def feature_active?(visitor_code, feature_key)
|
|
183
|
-
_, variation_key = _get_feature_variation_key(visitor_code, feature_key)
|
|
195
|
+
def feature_active?(visitor_code, feature_key, is_unique_identifier: false)
|
|
196
|
+
_, variation_key = _get_feature_variation_key(visitor_code, feature_key, is_unique_identifier)
|
|
184
197
|
variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
|
|
185
198
|
rescue Exception::FeatureEnvironmentDisabled
|
|
186
199
|
false
|
|
@@ -197,14 +210,16 @@ module Kameleoon
|
|
|
197
210
|
#
|
|
198
211
|
# @param [String] visitor_code
|
|
199
212
|
# @param [String] feature_key
|
|
213
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
214
|
+
# This field is optional.
|
|
200
215
|
#
|
|
201
216
|
# @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
|
|
202
217
|
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
|
|
203
218
|
# @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
|
|
204
219
|
# the current environment
|
|
205
220
|
#
|
|
206
|
-
def get_feature_variation_key(visitor_code, feature_key)
|
|
207
|
-
_, variation_key = _get_feature_variation_key(visitor_code, feature_key)
|
|
221
|
+
def get_feature_variation_key(visitor_code, feature_key, is_unique_identifier: false)
|
|
222
|
+
_, variation_key = _get_feature_variation_key(visitor_code, feature_key, is_unique_identifier)
|
|
208
223
|
variation_key
|
|
209
224
|
end
|
|
210
225
|
|
|
@@ -216,6 +231,8 @@ module Kameleoon
|
|
|
216
231
|
# @param [String] visitor_code
|
|
217
232
|
# @param [String] feature_key
|
|
218
233
|
# @param [String] variable_name
|
|
234
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
235
|
+
# This field is optional.
|
|
219
236
|
#
|
|
220
237
|
# @raise [Kameleoon::Exception::FeatureNotFound] Feature Flag isn't found in this configuration
|
|
221
238
|
# @raise [Kameleoon::Exception::FeatureVariableNotFound]
|
|
@@ -223,8 +240,8 @@ module Kameleoon
|
|
|
223
240
|
# @raise [Kameleoon::Exception::FeatureEnvironmentDisabled] If the requested feature flag is disabled for
|
|
224
241
|
# the current environment
|
|
225
242
|
#
|
|
226
|
-
def get_feature_variable(visitor_code, feature_key, variable_name)
|
|
227
|
-
feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key)
|
|
243
|
+
def get_feature_variable(visitor_code, feature_key, variable_name, is_unique_identifier: false)
|
|
244
|
+
feature_flag, variation_key = _get_feature_variation_key(visitor_code, feature_key, is_unique_identifier)
|
|
228
245
|
variation = feature_flag.get_variation_key(variation_key)
|
|
229
246
|
variable = variation&.get_variable_by_key(variable_name)
|
|
230
247
|
if variable.nil?
|
|
@@ -273,8 +290,7 @@ module Kameleoon
|
|
|
273
290
|
#
|
|
274
291
|
# @return [Hash] Hash object of the json object.
|
|
275
292
|
def get_remote_data(key, timeout = @default_timeout)
|
|
276
|
-
|
|
277
|
-
JSON.parse(response) if response
|
|
293
|
+
@remote_data_manager.get_data(key, timeout)
|
|
278
294
|
end
|
|
279
295
|
|
|
280
296
|
##
|
|
@@ -288,13 +304,12 @@ module Kameleoon
|
|
|
288
304
|
# for a visitor. If not specified, the default value is `True`. This field is optional.
|
|
289
305
|
# @param [Integer] timeout Timeout for request (in milliseconds). Equals default_timeout in a config file.
|
|
290
306
|
# This field is optional.
|
|
307
|
+
# @param [Bool] is_unique_identifier Parameter that specifies whether the visitorCode is a unique identifier.
|
|
308
|
+
# This field is optional.
|
|
291
309
|
#
|
|
292
310
|
# @return [Array] An array of data assigned to the given visitor.
|
|
293
|
-
def get_remote_visitor_data(visitor_code, timeout = nil, add_data: true)
|
|
294
|
-
|
|
295
|
-
data_array = parse_custom_data_array(visitor_code, response)
|
|
296
|
-
add_data(visitor_code, *data_array) if add_data && !data_array.empty?
|
|
297
|
-
data_array
|
|
311
|
+
def get_remote_visitor_data(visitor_code, timeout = nil, add_data: true, filter: nil, is_unique_identifier: false)
|
|
312
|
+
@remote_data_manager.get_visitor_data(visitor_code, add_data, filter, is_unique_identifier, timeout)
|
|
298
313
|
end
|
|
299
314
|
|
|
300
315
|
##
|
|
@@ -338,10 +353,15 @@ module Kameleoon
|
|
|
338
353
|
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
|
|
339
354
|
#
|
|
340
355
|
# @return [Array] array of active feature flag keys for a visitor
|
|
356
|
+
#
|
|
357
|
+
# DEPRECATED. Please use `get_active_features` instead.
|
|
341
358
|
def get_active_feature_list_for_visitor(visitor_code)
|
|
359
|
+
warn '[DEPRECATION] `get_active_feature_list_for_visitor` is deprecated. Please use `get_active_features` instead.'
|
|
342
360
|
Utils::VisitorCode.validate(visitor_code)
|
|
343
361
|
list_keys = []
|
|
344
362
|
@data_file.feature_flags.each do |feature_key, feature_flag|
|
|
363
|
+
next unless feature_flag.environment_enabled
|
|
364
|
+
|
|
345
365
|
variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
|
346
366
|
variation_key = _get_variation_key(variation, rule, feature_flag)
|
|
347
367
|
list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
|
|
@@ -349,6 +369,50 @@ module Kameleoon
|
|
|
349
369
|
list_keys
|
|
350
370
|
end
|
|
351
371
|
|
|
372
|
+
##
|
|
373
|
+
# Returns a Hash that contains the assigned variations of the active features using the keys
|
|
374
|
+
# of the corresponding active features.
|
|
375
|
+
#
|
|
376
|
+
# @param [String] visitor_code unique identifier of a visitor.
|
|
377
|
+
#
|
|
378
|
+
# @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars or is nil
|
|
379
|
+
#
|
|
380
|
+
# @return [Hash] Hash of active features for a visitor
|
|
381
|
+
def get_active_features(visitor_code)
|
|
382
|
+
Utils::VisitorCode.validate(visitor_code)
|
|
383
|
+
map_active_features = {}
|
|
384
|
+
|
|
385
|
+
@data_file.feature_flags.each_value do |feature_flag|
|
|
386
|
+
next unless feature_flag.environment_enabled
|
|
387
|
+
|
|
388
|
+
var_by_exp, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
|
389
|
+
variation_key = _get_variation_key(var_by_exp, rule, feature_flag)
|
|
390
|
+
|
|
391
|
+
next if variation_key == Configuration::VariationType::VARIATION_OFF
|
|
392
|
+
|
|
393
|
+
variation = feature_flag.get_variation_key(variation_key)
|
|
394
|
+
variables = {}
|
|
395
|
+
|
|
396
|
+
variation&.variables&.each do |variable|
|
|
397
|
+
variables[variable.key] = Kameleoon::Types::Variable.new(
|
|
398
|
+
variable.key,
|
|
399
|
+
variable.type,
|
|
400
|
+
_parse_feature_variable(variable)
|
|
401
|
+
)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
variables.freeze
|
|
405
|
+
map_active_features[feature_flag.feature_key] = Kameleoon::Types::Variation.new(
|
|
406
|
+
variation_key,
|
|
407
|
+
var_by_exp ? var_by_exp.variation_id : nil,
|
|
408
|
+
rule ? rule.experiment_id : nil,
|
|
409
|
+
variables
|
|
410
|
+
)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
map_active_features.freeze
|
|
414
|
+
end
|
|
415
|
+
|
|
352
416
|
##
|
|
353
417
|
# The `on_update_configuration()` method allows you to handle the event when configuration
|
|
354
418
|
# has updated data. It takes one input parameter: callable **handler**. The handler
|
|
@@ -471,16 +535,21 @@ module Kameleoon
|
|
|
471
535
|
return false unless response
|
|
472
536
|
|
|
473
537
|
configuration = JSON.parse(response)
|
|
474
|
-
@data_file = Configuration::DataFile.new(@config.environment).init(configuration)
|
|
475
|
-
@
|
|
476
|
-
@data_file.settings.is_consent_required && !@data_file.has_any_targeted_delivery_rule
|
|
477
|
-
@network_manager.url_provider.apply_data_api_domain(@data_file.settings.data_api_domain)
|
|
478
|
-
|
|
538
|
+
@data_file = Configuration::DataFile.new(@config.environment, method(:log)).init(configuration)
|
|
539
|
+
apply_new_configuration(@data_file)
|
|
479
540
|
call_update_handler_if_needed(!time_stamp.nil?)
|
|
480
541
|
log "Feature flags are fetched: #{response.inspect}"
|
|
481
542
|
true
|
|
482
543
|
end
|
|
483
544
|
|
|
545
|
+
def apply_new_configuration(data_file)
|
|
546
|
+
@cookie_manager.consent_required =
|
|
547
|
+
data_file.settings.is_consent_required && !data_file.has_any_targeted_delivery_rule
|
|
548
|
+
@network_manager.url_provider.apply_data_api_domain(data_file.settings.data_api_domain)
|
|
549
|
+
@targeting_manager = Kameleoon::Targeting::TargetingManager.new(@visitor_manager, data_file)
|
|
550
|
+
@visitor_manager.custom_data_info = data_file.custom_data_info
|
|
551
|
+
end
|
|
552
|
+
|
|
484
553
|
##
|
|
485
554
|
# Call the handler when configuraiton was updated with new time stamp.
|
|
486
555
|
#
|
|
@@ -513,47 +582,12 @@ module Kameleoon
|
|
|
513
582
|
# end
|
|
514
583
|
|
|
515
584
|
def check_targeting(visitor_code, campaign_id, exp_ff_rule)
|
|
516
|
-
|
|
517
|
-
return true if segment.nil?
|
|
518
|
-
|
|
519
|
-
visitor = @visitor_manager.get_visitor(visitor_code)
|
|
520
|
-
segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
def get_condition_data(type, visitor, visitor_code, campaign_id)
|
|
524
|
-
condition_data = nil
|
|
525
|
-
case type
|
|
526
|
-
when Kameleoon::Targeting::ConditionType::CUSTOM_DATUM
|
|
527
|
-
condition_data = visitor.custom_data unless visitor.nil?
|
|
528
|
-
when Kameleoon::Targeting::ConditionType::PAGE_TITLE,
|
|
529
|
-
Kameleoon::Targeting::ConditionType::PAGE_URL
|
|
530
|
-
condition_data = visitor.page_view_visits unless visitor.nil?
|
|
531
|
-
when Kameleoon::Targeting::ConditionType::DEVICE_TYPE
|
|
532
|
-
condition_data = visitor.device unless visitor.nil?
|
|
533
|
-
when Kameleoon::Targeting::ConditionType::BROWSER
|
|
534
|
-
condition_data = visitor.browser unless visitor.nil?
|
|
535
|
-
when Kameleoon::Targeting::ConditionType::CONVERSIONS
|
|
536
|
-
condition_data = visitor.conversions unless visitor.nil?
|
|
537
|
-
when Kameleoon::Targeting::ConditionType::SDK_LANGUAGE
|
|
538
|
-
condition_data = Kameleoon::Targeting::SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
|
|
539
|
-
when Kameleoon::Targeting::ConditionType::VISITOR_CODE
|
|
540
|
-
condition_data = visitor_code
|
|
541
|
-
when Kameleoon::Targeting::ConditionType::TARGET_EXPERIMENT
|
|
542
|
-
condition_data = visitor.variations unless visitor.nil?
|
|
543
|
-
when Kameleoon::Targeting::ConditionType::EXCLUSIVE_EXPERIMENT
|
|
544
|
-
unless visitor.nil?
|
|
545
|
-
condition_data = OpenStruct.new
|
|
546
|
-
condition_data.experiment_id = campaign_id
|
|
547
|
-
condition_data.storage = visitor.variations
|
|
548
|
-
end
|
|
549
|
-
end
|
|
550
|
-
condition_data
|
|
585
|
+
@targeting_manager.check_targeting(visitor_code, campaign_id, exp_ff_rule)
|
|
551
586
|
end
|
|
552
587
|
|
|
553
588
|
##
|
|
554
589
|
# helper method for getting variation key for feature flag
|
|
555
|
-
def _get_feature_variation_key(visitor_code, feature_key)
|
|
556
|
-
Utils::VisitorCode.validate(visitor_code)
|
|
590
|
+
def _get_feature_variation_key(visitor_code, feature_key, is_unique_identifier = false)
|
|
557
591
|
feature_flag = @data_file.get_feature_flag(feature_key)
|
|
558
592
|
variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
|
559
593
|
variation_key = _get_variation_key(variation, rule, feature_flag)
|
|
@@ -567,7 +601,7 @@ module Kameleoon
|
|
|
567
601
|
visitor.assign_variation(as_variation)
|
|
568
602
|
end
|
|
569
603
|
end
|
|
570
|
-
|
|
604
|
+
flush(visitor_code, is_unique_identifier: is_unique_identifier)
|
|
571
605
|
[feature_flag, variation_key]
|
|
572
606
|
end
|
|
573
607
|
|
|
@@ -577,16 +611,19 @@ module Kameleoon
|
|
|
577
611
|
# no rules -> return default_variation_key
|
|
578
612
|
feature_flag.rules.each do |rule|
|
|
579
613
|
# check if visitor is targeted for rule, else next rule
|
|
580
|
-
next unless check_targeting(visitor_code,
|
|
614
|
+
next unless check_targeting(visitor_code, rule.experiment_id, rule)
|
|
581
615
|
|
|
616
|
+
vis = @visitor_manager.get_visitor(visitor_code)
|
|
617
|
+
# use mappingIdentifier instead of visitorCode if it was set up
|
|
618
|
+
code_for_hash = vis&.mapping_identifier || visitor_code
|
|
582
619
|
# uses for rule exposition
|
|
583
|
-
hash_rule = Utils::HashDouble.obtain_rule(
|
|
620
|
+
hash_rule = Utils::HashDouble.obtain_rule(code_for_hash, rule.id, rule.respool_time)
|
|
584
621
|
# check main expostion for rule with hashRule
|
|
585
622
|
if hash_rule <= rule.exposition
|
|
586
|
-
return [rule.
|
|
623
|
+
return [rule.first_variation, rule] if rule.targeted_delivery_type?
|
|
587
624
|
|
|
588
625
|
# uses for variation's expositions
|
|
589
|
-
hash_variation = Utils::HashDouble.obtain_rule(
|
|
626
|
+
hash_variation = Utils::HashDouble.obtain_rule(code_for_hash, rule.experiment_id, rule.respool_time)
|
|
590
627
|
# get variation key with new hashVariation
|
|
591
628
|
variation = rule.get_variation(hash_variation)
|
|
592
629
|
return [variation, rule] unless variation.nil?
|
|
@@ -607,11 +644,12 @@ module Kameleoon
|
|
|
607
644
|
|
|
608
645
|
##
|
|
609
646
|
# helper method for sending tracking requests for new FF
|
|
610
|
-
def _send_tracking_request(visitor_code, visitor = nil, force_request = true)
|
|
647
|
+
def _send_tracking_request(visitor_code, visitor = nil, force_request = true, is_unique_identifier = false)
|
|
611
648
|
if visitor.nil?
|
|
612
649
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
|
613
650
|
return if visitor.nil? && @data_file.settings.is_consent_required
|
|
614
651
|
end
|
|
652
|
+
use_mapping_value, visitor = create_anonymous_if_required(visitor_code, visitor, is_unique_identifier)
|
|
615
653
|
consent = consent_provided?(visitor)
|
|
616
654
|
user_agent = visitor&.user_agent
|
|
617
655
|
unsent = visitor.nil? ? [] : select_unsent_data(visitor, consent)
|
|
@@ -621,7 +659,20 @@ module Kameleoon
|
|
|
621
659
|
unsent.push(Network::ActivityEvent.new)
|
|
622
660
|
end
|
|
623
661
|
log "Start post tracking: #{unsent.inspect}"
|
|
624
|
-
@network_manager.send_tracking_data(visitor_code, unsent, user_agent)
|
|
662
|
+
@network_manager.send_tracking_data(visitor_code, unsent, user_agent, use_mapping_value)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def create_anonymous_if_required(visitor_code, visitor, is_unique_identifier)
|
|
666
|
+
use_mapping_value = is_unique_identifier && !visitor&.mapping_identifier.nil?
|
|
667
|
+
# need to find if anonymous visitor is behind unique (anonym doesn't exist if MappingIdentifier == null)
|
|
668
|
+
if is_unique_identifier && visitor&.mapping_identifier.nil?
|
|
669
|
+
# We haven't anonymous behind, in this case we should create "fake" anonymous with id == visitorCode
|
|
670
|
+
# and link it with with mapping value == visitorCode (like we do as we have real anonymous visitor)
|
|
671
|
+
visitor = @visitor_manager.add_data(visitor_code,
|
|
672
|
+
CustomData.new(@data_file.custom_data_info.mapping_identifier_index,
|
|
673
|
+
visitor_code))
|
|
674
|
+
end
|
|
675
|
+
[use_mapping_value, visitor]
|
|
625
676
|
end
|
|
626
677
|
|
|
627
678
|
def select_unsent_data(visitor, consent)
|
|
@@ -654,29 +705,6 @@ module Kameleoon
|
|
|
654
705
|
end
|
|
655
706
|
end
|
|
656
707
|
|
|
657
|
-
##
|
|
658
|
-
# helper method used by `get_remote_visitor_data`
|
|
659
|
-
def parse_custom_data_array(visitor_code, response)
|
|
660
|
-
return [] unless response
|
|
661
|
-
|
|
662
|
-
raw = JSON.parse(response)
|
|
663
|
-
latest_record = raw['currentVisit']
|
|
664
|
-
if latest_record.nil?
|
|
665
|
-
previous_visits = raw['previousVisits']
|
|
666
|
-
return [] unless previous_visits
|
|
667
|
-
|
|
668
|
-
latest_record = previous_visits.first
|
|
669
|
-
end
|
|
670
|
-
events = latest_record['customDataEvents']
|
|
671
|
-
return [] if events.nil?
|
|
672
|
-
|
|
673
|
-
events.map { |ev| ev['data'] }.compact
|
|
674
|
-
.map { |jcd| CustomData.new(jcd['index'] || -1, *(jcd['valuesCountMap'] || {}).keys) }
|
|
675
|
-
rescue StandardError => e
|
|
676
|
-
log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
|
|
677
|
-
[]
|
|
678
|
-
end
|
|
679
|
-
|
|
680
708
|
def consent_provided?(visitor)
|
|
681
709
|
!@data_file.settings.is_consent_required || visitor&.legal_consent
|
|
682
710
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kameleoon/managers/remote_data/remote_visitor_data'
|
|
4
|
+
require 'kameleoon/types/remote_visitor_data_filter'
|
|
5
|
+
|
|
6
|
+
module Kameleoon
|
|
7
|
+
module Managers
|
|
8
|
+
module RemoteData
|
|
9
|
+
class RemoteDataManager
|
|
10
|
+
attr_reader :network_manager, :visitor_manger, :log_func
|
|
11
|
+
|
|
12
|
+
def initialize(network_manager, visitor_manger, log_func = nil)
|
|
13
|
+
@network_manager = network_manager
|
|
14
|
+
@visitor_manger = visitor_manger
|
|
15
|
+
@log_func = log_func
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_data(key, timeout)
|
|
19
|
+
response = @network_manager.get_remote_data(key, timeout)
|
|
20
|
+
JSON.parse(response) if response
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
log("Parsing of visitor data of '#{key}' failed: #{e}")
|
|
23
|
+
raise
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_visitor_data(visitor_code, add_data, filter = nil, is_unique_identifier = false, timeout = nil)
|
|
27
|
+
Utils::VisitorCode.validate(visitor_code)
|
|
28
|
+
filter = Kameleoon::Types::RemoteVisitorDataFilter.new unless filter.is_a?(Kameleoon::Types::RemoteVisitorDataFilter)
|
|
29
|
+
response = @network_manager.get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout)
|
|
30
|
+
(data_to_add, data_to_return) = parse_custom_data_array(visitor_code, response)
|
|
31
|
+
if add_data && !data_to_add.empty?
|
|
32
|
+
visitor = @visitor_manger.get_or_create_visitor(visitor_code)
|
|
33
|
+
visitor.add_data(@log_func, *data_to_add, overwrite: false)
|
|
34
|
+
end
|
|
35
|
+
data_to_return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# helper method used by `get_remote_visitor_data`
|
|
40
|
+
def parse_custom_data_array(visitor_code, response)
|
|
41
|
+
remote_visitor_data = Kameleoon::Managers::RemoteData::RemoteVisitorData.new(JSON.parse(response))
|
|
42
|
+
remote_visitor_data.mark_data_as_sent(@visitor_manger.custom_data_info)
|
|
43
|
+
[remote_visitor_data.collect_data_to_add, remote_visitor_data.collect_data_to_return]
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
|
|
46
|
+
raise
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def log(text)
|
|
50
|
+
@log_func&.call(text)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kameleoon/data/manager/page_view_visit'
|
|
4
|
+
require 'kameleoon/data/manager/assigned_variation'
|
|
5
|
+
require 'kameleoon/data/visitor_visits'
|
|
6
|
+
require 'kameleoon/data/browser'
|
|
7
|
+
require 'kameleoon/data/device'
|
|
8
|
+
require 'kameleoon/data/conversion'
|
|
9
|
+
require 'kameleoon/data/custom_data'
|
|
10
|
+
require 'kameleoon/data/geolocation'
|
|
11
|
+
require 'kameleoon/data/kcs_heat'
|
|
12
|
+
require 'kameleoon/data/page_view'
|
|
13
|
+
|
|
14
|
+
module Kameleoon
|
|
15
|
+
module Managers
|
|
16
|
+
module RemoteData
|
|
17
|
+
class RemoteVisitorData
|
|
18
|
+
attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :device, :browser,
|
|
19
|
+
:operating_system, :geolocation, :previous_visitor_visits, :kcs_heat
|
|
20
|
+
|
|
21
|
+
def initialize(hash)
|
|
22
|
+
current_visit = hash['currentVisit']
|
|
23
|
+
parse_visit(current_visit) unless current_visit.nil?
|
|
24
|
+
previous_visits = hash['previousVisits']
|
|
25
|
+
previous_visits = [] if previous_visits.nil?
|
|
26
|
+
|
|
27
|
+
if previous_visits.size.positive?
|
|
28
|
+
times_started = []
|
|
29
|
+
previous_visits.each do |visit|
|
|
30
|
+
times_started.push(visit['timeStarted'])
|
|
31
|
+
parse_visit(visit)
|
|
32
|
+
end
|
|
33
|
+
@previous_visitor_visits = VisitorVisits.new(times_started)
|
|
34
|
+
end
|
|
35
|
+
@kcs_heat = parse_kcs_heat(hash['kcs'])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def collect_data_to_add
|
|
39
|
+
data_to_add = []
|
|
40
|
+
data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
|
|
41
|
+
data_to_add.push(@previous_visitor_visits) unless @previous_visitor_visits.nil?
|
|
42
|
+
data_to_add.push(@kcs_heat) unless @kcs_heat.nil?
|
|
43
|
+
data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
|
|
44
|
+
data_to_add.concat(@experiments.values) unless @experiments.nil?
|
|
45
|
+
data_to_add.concat(conversions_single_objects)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def collect_data_to_return
|
|
49
|
+
data_to_return = []
|
|
50
|
+
data_to_return.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
|
|
51
|
+
@page_view_visits&.each_value do |visit|
|
|
52
|
+
data_to_return.push(visit.page_view)
|
|
53
|
+
end
|
|
54
|
+
data_to_return.concat(conversions_single_objects)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def mark_data_as_sent(custom_data_info)
|
|
58
|
+
@custom_data_dict&.each_value do |data|
|
|
59
|
+
data.mark_as_sent unless custom_data_info.visitor_scope?(data.id)
|
|
60
|
+
end
|
|
61
|
+
@experiments&.each_value(&:mark_as_sent)
|
|
62
|
+
@page_view_visits&.each_value do |visit|
|
|
63
|
+
visit.page_view.mark_as_sent
|
|
64
|
+
end
|
|
65
|
+
conversions_single_objects.each(&:mark_as_sent)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def parse_visit(hash)
|
|
71
|
+
custom_data_events = hash['customDataEvents']
|
|
72
|
+
parse_custom_data(custom_data_events) if custom_data_events != nil && custom_data_events.size.positive?
|
|
73
|
+
page_events = hash['pageEvents']
|
|
74
|
+
parse_pages(page_events) if page_events != nil && page_events.size.positive?
|
|
75
|
+
experiment_events = hash['experimentEvents']
|
|
76
|
+
parse_experiments(experiment_events) if experiment_events != nil && experiment_events.size.positive?
|
|
77
|
+
conversion_events = hash['conversionEvents']
|
|
78
|
+
parse_conversions(conversion_events) if conversion_events != nil && conversion_events.size.positive?
|
|
79
|
+
geolocation_events = hash['geolocationEvents']
|
|
80
|
+
@geolocation = parse_geolocation(geolocation_events) if @geolocation.nil? && geolocation_events != nil && geolocation_events.size.positive?
|
|
81
|
+
static_data_events = hash['staticDataEvent']
|
|
82
|
+
parse_static_data(static_data_events) if static_data_events != nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_custom_data(custom_data_events)
|
|
86
|
+
@custom_data_dict = {} if @custom_data_dict.nil?
|
|
87
|
+
custom_data_events.reverse_each do |custom_data_event|
|
|
88
|
+
data = custom_data_event['data']
|
|
89
|
+
id = data['index']
|
|
90
|
+
id = -1 if id.nil?
|
|
91
|
+
@custom_data_dict[id] = CustomData.new(id, *data['valuesCountMap'].keys) unless @custom_data_dict.include?(id)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_pages(page_events)
|
|
96
|
+
@page_view_visits = {} if @page_view_visits.nil?
|
|
97
|
+
page_events.reverse_each do |page_event|
|
|
98
|
+
href = page_event['data']['href']
|
|
99
|
+
page_view_visit = @page_view_visits[href]
|
|
100
|
+
if page_view_visit.nil?
|
|
101
|
+
page_view = PageView.new(href, page_event['data']['title'])
|
|
102
|
+
@page_view_visits[href] = DataManager::PageViewVisit.new(page_view, 1, page_event['time'])
|
|
103
|
+
else
|
|
104
|
+
page_view_visit.increase_page_visits
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_experiments(experiment_events)
|
|
110
|
+
@experiments = {} if @experiments.nil?
|
|
111
|
+
experiment_events.reverse_each do |experiment_event|
|
|
112
|
+
id = experiment_event['data']['id']
|
|
113
|
+
variation = @experiments[id]
|
|
114
|
+
if variation.nil?
|
|
115
|
+
variation = DataManager::AssignedVariation.new(
|
|
116
|
+
id, experiment_event['data']['variationId'],
|
|
117
|
+
Configuration::RuleType::UNKNOWN, assignment_time: experiment_event['time']
|
|
118
|
+
)
|
|
119
|
+
@experiments[id] = variation
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parse_conversions(conversion_events)
|
|
125
|
+
@conversions = [] if @conversions.nil?
|
|
126
|
+
conversion_events.each do |conversion_event|
|
|
127
|
+
data = conversion_event['data']
|
|
128
|
+
conversion = Conversion.new(data['goalId'], data['revenue'], data['negative'])
|
|
129
|
+
@conversions.push(conversion)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_geolocation(geolocation_events)
|
|
134
|
+
data = geolocation_events[geolocation_events.size - 1]['data']
|
|
135
|
+
Geolocation.new(data['country'], data['region'], data['city'])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_static_data(static_data_event)
|
|
139
|
+
return if @device != nil && @browser != nil && @operating_system != nil
|
|
140
|
+
|
|
141
|
+
data = static_data_event['data']
|
|
142
|
+
if @device.nil?
|
|
143
|
+
device_type = data['deviceType']
|
|
144
|
+
@device = Device.new(device_type) unless device_type.nil?
|
|
145
|
+
end
|
|
146
|
+
if @browser.nil?
|
|
147
|
+
browser_type = BrowserType.from_name(data['browser'])
|
|
148
|
+
@browser = Browser.new(browser_type, data['browserVersion']) unless browser_type.nil?
|
|
149
|
+
end
|
|
150
|
+
if @operating_system.nil?
|
|
151
|
+
operating_system_type = OperatingSystemType.from_name(data['os'])
|
|
152
|
+
@operating_system = OperatingSystem.new(operating_system_type) unless operating_system_type.nil?
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def conversions_single_objects
|
|
157
|
+
objects = []
|
|
158
|
+
objects += @conversions unless @conversions.nil?
|
|
159
|
+
objects.push(@device) unless @device.nil?
|
|
160
|
+
objects.push(@browser) unless @browser.nil?
|
|
161
|
+
objects.push(@operating_system) unless @operating_system.nil?
|
|
162
|
+
objects.push(@geolocation) unless @geolocation.nil?
|
|
163
|
+
objects
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def parse_kcs_heat(kcs)
|
|
167
|
+
return nil if kcs.nil?
|
|
168
|
+
|
|
169
|
+
value_map = {}
|
|
170
|
+
kcs.each do |str_key_moment_id, goal_scores|
|
|
171
|
+
next unless str_key_moment_id.is_a?(String) && goal_scores.is_a?(Hash)
|
|
172
|
+
|
|
173
|
+
goal_score_map = {}
|
|
174
|
+
goal_scores.each do |str_goal_id, score|
|
|
175
|
+
next unless str_goal_id.is_a?(String) && (score.is_a?(Float) || score.is_a?(Integer))
|
|
176
|
+
|
|
177
|
+
goal_id = str_goal_id.to_i
|
|
178
|
+
goal_score_map[goal_id] = score
|
|
179
|
+
end
|
|
180
|
+
key_moment_id = str_key_moment_id.to_i
|
|
181
|
+
value_map[key_moment_id] = goal_score_map
|
|
182
|
+
end
|
|
183
|
+
KcsHeat.new(value_map)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -19,7 +19,9 @@ module Kameleoon
|
|
|
19
19
|
Utils::VisitorCode.validate(visitor_code)
|
|
20
20
|
remote_data_key = warehouse_key.nil? || warehouse_key.empty? ? visitor_code : warehouse_key
|
|
21
21
|
response = @network_manager.get_remote_data(remote_data_key, timeout)
|
|
22
|
-
|
|
22
|
+
return nil unless response.is_a?(String)
|
|
23
|
+
|
|
24
|
+
remote_data = JSON.parse(response)
|
|
23
25
|
warehouse_audiences = remote_data.is_a?(Hash) ? remote_data[WAREHOUSE_AUDIENCES_FIELD_NAME] : nil
|
|
24
26
|
data_values = warehouse_audiences.is_a?(Hash) ? warehouse_audiences.keys : []
|
|
25
27
|
warehouse_audiences_data = CustomData.new(custom_data_index, *data_values)
|