kameleoon-client-ruby 3.1.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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)