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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/configuration/custom_data_info.rb +43 -0
  3. data/lib/kameleoon/configuration/data_file.rb +31 -2
  4. data/lib/kameleoon/configuration/rule.rb +4 -1
  5. data/lib/kameleoon/configuration/variation_exposition.rb +1 -1
  6. data/lib/kameleoon/data/browser.rb +19 -0
  7. data/lib/kameleoon/data/cookie.rb +14 -0
  8. data/lib/kameleoon/data/custom_data.rb +2 -0
  9. data/lib/kameleoon/data/data.rb +2 -0
  10. data/lib/kameleoon/data/geolocation.rb +45 -0
  11. data/lib/kameleoon/data/kcs_heat.rb +12 -0
  12. data/lib/kameleoon/data/manager/assigned_variation.rb +2 -1
  13. data/lib/kameleoon/data/manager/page_view_visit.rb +16 -2
  14. data/lib/kameleoon/data/manager/visitor.rb +71 -14
  15. data/lib/kameleoon/data/manager/visitor_manager.rb +23 -1
  16. data/lib/kameleoon/data/operating_system.rb +72 -0
  17. data/lib/kameleoon/data/page_view.rb +2 -2
  18. data/lib/kameleoon/data/visitor_visits.rb +20 -0
  19. data/lib/kameleoon/kameleoon_client.rb +122 -94
  20. data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +55 -0
  21. data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +188 -0
  22. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +3 -1
  23. data/lib/kameleoon/network/access_token_source.rb +22 -5
  24. data/lib/kameleoon/network/network_manager.rb +10 -10
  25. data/lib/kameleoon/network/response.rb +1 -1
  26. data/lib/kameleoon/network/url_provider.rb +16 -12
  27. data/lib/kameleoon/targeting/condition.rb +24 -2
  28. data/lib/kameleoon/targeting/condition_factory.rb +39 -6
  29. data/lib/kameleoon/targeting/conditions/cookie_condition.rb +55 -0
  30. data/lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb +27 -0
  31. data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +33 -0
  32. data/lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb +37 -0
  33. data/lib/kameleoon/targeting/conditions/number_condition.rb +38 -0
  34. data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +27 -0
  35. data/lib/kameleoon/targeting/conditions/page_view_number_condition.rb +28 -0
  36. data/lib/kameleoon/targeting/conditions/previous_page_condition.rb +35 -0
  37. data/lib/kameleoon/targeting/conditions/segment_condition.rb +42 -0
  38. data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +59 -0
  39. data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +29 -0
  40. data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +28 -0
  41. data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +23 -0
  42. data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +34 -0
  43. data/lib/kameleoon/targeting/targeting_manager.rb +68 -0
  44. data/lib/kameleoon/types/remote_visitor_data_filter.rb +29 -0
  45. data/lib/kameleoon/types/variable.rb +17 -0
  46. data/lib/kameleoon/types/variation.rb +18 -0
  47. data/lib/kameleoon/utils.rb +1 -0
  48. data/lib/kameleoon/version.rb +1 -1
  49. metadata +28 -4
  50. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +0 -18
  51. 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
- visitor = @visitor_manager.get_or_create_visitor(visitor_code)
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 { |visitor_code, visitor| _send_tracking_request(visitor_code, visitor, false) }
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
- response = @network_manager.get_remote_data(key, timeout)
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
- response = @network_manager.get_remote_visitor_data(visitor_code, timeout)
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
- @cookie_manager.consent_required =
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
- segment = exp_ff_rule.targeting_segment
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
- _send_tracking_request(visitor_code, visitor)
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, feature_flag.id, rule)
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(visitor_code, rule.id, rule.respool_time)
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.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
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(visitor_code, rule.experiment_id, rule.respool_time)
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
- remote_data = response.is_a?(String) ? JSON.parse(response) : nil
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)