kameleoon-client-ruby 3.1.1 → 3.2.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 (47) 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 +30 -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/manager/assigned_variation.rb +2 -1
  12. data/lib/kameleoon/data/manager/page_view_visit.rb +16 -2
  13. data/lib/kameleoon/data/manager/visitor.rb +64 -14
  14. data/lib/kameleoon/data/manager/visitor_manager.rb +23 -1
  15. data/lib/kameleoon/data/operating_system.rb +72 -0
  16. data/lib/kameleoon/data/page_view.rb +2 -2
  17. data/lib/kameleoon/data/visitor_visits.rb +13 -0
  18. data/lib/kameleoon/kameleoon_client.rb +75 -94
  19. data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +57 -0
  20. data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +162 -0
  21. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +3 -1
  22. data/lib/kameleoon/network/access_token_source.rb +22 -5
  23. data/lib/kameleoon/network/network_manager.rb +10 -10
  24. data/lib/kameleoon/network/response.rb +1 -1
  25. data/lib/kameleoon/network/url_provider.rb +15 -12
  26. data/lib/kameleoon/targeting/condition.rb +22 -2
  27. data/lib/kameleoon/targeting/condition_factory.rb +36 -6
  28. data/lib/kameleoon/targeting/conditions/cookie_condition.rb +55 -0
  29. data/lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb +27 -0
  30. data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +33 -0
  31. data/lib/kameleoon/targeting/conditions/number_condition.rb +38 -0
  32. data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +27 -0
  33. data/lib/kameleoon/targeting/conditions/page_view_number_condition.rb +28 -0
  34. data/lib/kameleoon/targeting/conditions/previous_page_condition.rb +35 -0
  35. data/lib/kameleoon/targeting/conditions/segment_condition.rb +42 -0
  36. data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +63 -0
  37. data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +30 -0
  38. data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +28 -0
  39. data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +21 -0
  40. data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +33 -0
  41. data/lib/kameleoon/targeting/targeting_manager.rb +66 -0
  42. data/lib/kameleoon/types/remote_visitor_data_filter.rb +24 -0
  43. data/lib/kameleoon/utils.rb +1 -0
  44. data/lib/kameleoon/version.rb +1 -1
  45. metadata +24 -4
  46. data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +0 -18
  47. 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
  ##
@@ -342,6 +357,8 @@ module Kameleoon
342
357
  Utils::VisitorCode.validate(visitor_code)
343
358
  list_keys = []
344
359
  @data_file.feature_flags.each do |feature_key, feature_flag|
360
+ next unless feature_flag.environment_enabled
361
+
345
362
  variation, rule, = _calculate_variation_key_for_feature(visitor_code, feature_flag)
346
363
  variation_key = _get_variation_key(variation, rule, feature_flag)
347
364
  list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
@@ -471,16 +488,21 @@ module Kameleoon
471
488
  return false unless response
472
489
 
473
490
  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
-
491
+ @data_file = Configuration::DataFile.new(@config.environment, method(:log)).init(configuration)
492
+ apply_new_configuration(@data_file)
479
493
  call_update_handler_if_needed(!time_stamp.nil?)
480
494
  log "Feature flags are fetched: #{response.inspect}"
481
495
  true
482
496
  end
483
497
 
498
+ def apply_new_configuration(data_file)
499
+ @cookie_manager.consent_required =
500
+ data_file.settings.is_consent_required && !data_file.has_any_targeted_delivery_rule
501
+ @network_manager.url_provider.apply_data_api_domain(data_file.settings.data_api_domain)
502
+ @targeting_manager = Kameleoon::Targeting::TargetingManager.new(@visitor_manager, data_file)
503
+ @visitor_manager.custom_data_info = data_file.custom_data_info
504
+ end
505
+
484
506
  ##
485
507
  # Call the handler when configuraiton was updated with new time stamp.
486
508
  #
@@ -513,47 +535,12 @@ module Kameleoon
513
535
  # end
514
536
 
515
537
  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
538
+ @targeting_manager.check_targeting(visitor_code, campaign_id, exp_ff_rule)
551
539
  end
552
540
 
553
541
  ##
554
542
  # 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)
543
+ def _get_feature_variation_key(visitor_code, feature_key, is_unique_identifier = false)
557
544
  feature_flag = @data_file.get_feature_flag(feature_key)
558
545
  variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
559
546
  variation_key = _get_variation_key(variation, rule, feature_flag)
@@ -567,7 +554,7 @@ module Kameleoon
567
554
  visitor.assign_variation(as_variation)
568
555
  end
569
556
  end
570
- _send_tracking_request(visitor_code, visitor)
557
+ flush(visitor_code, is_unique_identifier: is_unique_identifier)
571
558
  [feature_flag, variation_key]
572
559
  end
573
560
 
@@ -577,16 +564,19 @@ module Kameleoon
577
564
  # no rules -> return default_variation_key
578
565
  feature_flag.rules.each do |rule|
579
566
  # check if visitor is targeted for rule, else next rule
580
- next unless check_targeting(visitor_code, feature_flag.id, rule)
567
+ next unless check_targeting(visitor_code, rule.experiment_id, rule)
581
568
 
569
+ vis = @visitor_manager.get_visitor(visitor_code)
570
+ # use mappingIdentifier instead of visitorCode if it was set up
571
+ code_for_hash = vis&.mapping_identifier || visitor_code
582
572
  # uses for rule exposition
583
- hash_rule = Utils::HashDouble.obtain_rule(visitor_code, rule.id, rule.respool_time)
573
+ hash_rule = Utils::HashDouble.obtain_rule(code_for_hash, rule.id, rule.respool_time)
584
574
  # check main expostion for rule with hashRule
585
575
  if hash_rule <= rule.exposition
586
- return [rule.variation_by_exposition[0], rule] if rule.targeted_delivery_type?
576
+ return [rule.first_variation, rule] if rule.targeted_delivery_type?
587
577
 
588
578
  # uses for variation's expositions
589
- hash_variation = Utils::HashDouble.obtain_rule(visitor_code, rule.experiment_id, rule.respool_time)
579
+ hash_variation = Utils::HashDouble.obtain_rule(code_for_hash, rule.experiment_id, rule.respool_time)
590
580
  # get variation key with new hashVariation
591
581
  variation = rule.get_variation(hash_variation)
592
582
  return [variation, rule] unless variation.nil?
@@ -607,11 +597,12 @@ module Kameleoon
607
597
 
608
598
  ##
609
599
  # helper method for sending tracking requests for new FF
610
- def _send_tracking_request(visitor_code, visitor = nil, force_request = true)
600
+ def _send_tracking_request(visitor_code, visitor = nil, force_request = true, is_unique_identifier = false)
611
601
  if visitor.nil?
612
602
  visitor = @visitor_manager.get_visitor(visitor_code)
613
603
  return if visitor.nil? && @data_file.settings.is_consent_required
614
604
  end
605
+ use_mapping_value, visitor = create_anonymous_if_required(visitor_code, visitor, is_unique_identifier)
615
606
  consent = consent_provided?(visitor)
616
607
  user_agent = visitor&.user_agent
617
608
  unsent = visitor.nil? ? [] : select_unsent_data(visitor, consent)
@@ -621,7 +612,20 @@ module Kameleoon
621
612
  unsent.push(Network::ActivityEvent.new)
622
613
  end
623
614
  log "Start post tracking: #{unsent.inspect}"
624
- @network_manager.send_tracking_data(visitor_code, unsent, user_agent)
615
+ @network_manager.send_tracking_data(visitor_code, unsent, user_agent, use_mapping_value)
616
+ end
617
+
618
+ def create_anonymous_if_required(visitor_code, visitor, is_unique_identifier)
619
+ use_mapping_value = is_unique_identifier && !visitor&.mapping_identifier.nil?
620
+ # need to find if anonymous visitor is behind unique (anonym doesn't exist if MappingIdentifier == null)
621
+ if is_unique_identifier && visitor&.mapping_identifier.nil?
622
+ # We haven't anonymous behind, in this case we should create "fake" anonymous with id == visitorCode
623
+ # and link it with with mapping value == visitorCode (like we do as we have real anonymous visitor)
624
+ visitor = @visitor_manager.add_data(visitor_code,
625
+ CustomData.new(@data_file.custom_data_info.mapping_identifier_index,
626
+ visitor_code))
627
+ end
628
+ [use_mapping_value, visitor]
625
629
  end
626
630
 
627
631
  def select_unsent_data(visitor, consent)
@@ -654,29 +658,6 @@ module Kameleoon
654
658
  end
655
659
  end
656
660
 
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
661
  def consent_provided?(visitor)
681
662
  !@data_file.settings.is_consent_required || visitor&.legal_consent
682
663
  end
@@ -0,0 +1,57 @@
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
+
10
+ class RemoteDataManager
11
+ attr_reader :network_manager, :visitor_manger, :log_func
12
+
13
+ def initialize(network_manager, visitor_manger, log_func = nil)
14
+ @network_manager = network_manager
15
+ @visitor_manger = visitor_manger
16
+ @log_func = log_func
17
+ end
18
+
19
+ def get_data(key, timeout)
20
+ response = @network_manager.get_remote_data(key, timeout)
21
+ JSON.parse(response) if response
22
+ rescue StandardError => e
23
+ log("Parsing of visitor data of '#{key}' failed: #{e}")
24
+ raise
25
+ end
26
+
27
+ def get_visitor_data(visitor_code, add_data, filter = nil, is_unique_identifier = false, timeout = nil)
28
+ Utils::VisitorCode.validate(visitor_code)
29
+ filter = Kameleoon::Types::RemoteVisitorDataFilter.new unless filter.is_a?(Kameleoon::Types::RemoteVisitorDataFilter)
30
+ response = @network_manager.get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout)
31
+ (data_to_add, data_to_return) = parse_custom_data_array(visitor_code, response)
32
+ if add_data && !data_to_add.empty?
33
+ visitor = @visitor_manger.get_or_create_visitor(visitor_code)
34
+ visitor.add_data(@log_func, *data_to_add, overwrite: false)
35
+ end
36
+ data_to_return
37
+ end
38
+
39
+
40
+ ##
41
+ # helper method used by `get_remote_visitor_data`
42
+ def parse_custom_data_array(visitor_code, response)
43
+ remote_visitor_data = Kameleoon::Managers::RemoteData::RemoteVisitorData.new(JSON.parse(response))
44
+ remote_visitor_data.mark_data_as_sent(@visitor_manger.custom_data_info)
45
+ [remote_visitor_data.collect_data_to_add, remote_visitor_data.collect_data_to_return]
46
+ rescue StandardError => e
47
+ log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
48
+ raise
49
+ end
50
+
51
+ def log(text)
52
+ @log_func&.call(text)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,162 @@
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/page_view'
12
+
13
+ module Kameleoon
14
+ module Managers
15
+ module RemoteData
16
+ class RemoteVisitorData
17
+ attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :device, :browser, :operating_system, :geolocation, :previous_visitor_visits
18
+
19
+ def initialize(hash)
20
+ current_visit = hash['currentVisit']
21
+ parse_visit(current_visit) unless current_visit.nil?
22
+ previous_visits = hash['previousVisits']
23
+ previous_visits = [] if previous_visits.nil?
24
+
25
+ if previous_visits.size.positive?
26
+ times_started = []
27
+ previous_visits.each do |visit|
28
+ times_started.push(visit['timeStarted'])
29
+ parse_visit(visit)
30
+ end
31
+ @previous_visitor_visits = VisitorVisits.new(times_started)
32
+ end
33
+ end
34
+
35
+ def collect_data_to_add
36
+ data_to_add = []
37
+ data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
38
+ data_to_add.push(@previous_visitor_visits) unless @previous_visitor_visits.nil?
39
+ data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
40
+ data_to_add.concat(@experiments.values) unless @experiments.nil?
41
+ data_to_add.concat(conversions_single_objects)
42
+ end
43
+
44
+ def collect_data_to_return
45
+ data_to_return = []
46
+ data_to_return.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
47
+ @page_view_visits&.each_value do |visit|
48
+ data_to_return.push(visit.page_view)
49
+ end
50
+ data_to_return.concat(conversions_single_objects)
51
+ end
52
+
53
+ def mark_data_as_sent(custom_data_info)
54
+ @custom_data_dict&.each_value do |data|
55
+ data.mark_as_sent unless custom_data_info.visitor_scope?(data.id)
56
+ end
57
+ @experiments&.each_value(&:mark_as_sent)
58
+ @page_view_visits&.each_value do |visit|
59
+ visit.page_view.mark_as_sent
60
+ end
61
+ conversions_single_objects.each(&:mark_as_sent)
62
+ end
63
+
64
+ private
65
+
66
+ def parse_visit(hash)
67
+ custom_data_events = hash['customDataEvents']
68
+ parse_custom_data(custom_data_events) if custom_data_events != nil && custom_data_events.size.positive?
69
+ page_events = hash['pageEvents']
70
+ parse_pages(page_events) if page_events != nil && page_events.size.positive?
71
+ experiment_events = hash['experimentEvents']
72
+ parse_experiments(experiment_events) if experiment_events != nil && experiment_events.size.positive?
73
+ conversion_events = hash['conversionEvents']
74
+ parse_conversions(conversion_events) if conversion_events != nil && conversion_events.size.positive?
75
+ geolocation_events = hash['geolocationEvents']
76
+ @geolocation = parse_geolocation(geolocation_events) if @geolocation.nil? && geolocation_events != nil && geolocation_events.size.positive?
77
+ static_data_events = hash['staticDataEvent']
78
+ parse_static_data(static_data_events) if static_data_events != nil
79
+ end
80
+
81
+ def parse_custom_data(custom_data_events)
82
+ @custom_data_dict = {} if @custom_data_dict.nil?
83
+ custom_data_events.reverse_each do |custom_data_event|
84
+ data = custom_data_event['data']
85
+ id = data['index']
86
+ id = -1 if id.nil?
87
+ @custom_data_dict[id] = CustomData.new(id, *data['valuesCountMap'].keys) unless @custom_data_dict.include?(id)
88
+ end
89
+ end
90
+
91
+ def parse_pages(page_events)
92
+ @page_view_visits = {} if @page_view_visits.nil?
93
+ page_events.reverse_each do |page_event|
94
+ href = page_event['data']['href']
95
+ page_view_visit = @page_view_visits[href]
96
+ if page_view_visit.nil?
97
+ page_view = PageView.new(href, page_event['data']['title'])
98
+ @page_view_visits[href] = Kameleoon::DataManager::PageViewVisit.new(page_view, 1, page_event['time'])
99
+ else
100
+ page_view_visit.increase_page_visits
101
+ end
102
+ end
103
+ end
104
+
105
+ def parse_experiments(experiment_events)
106
+ @experiments = {} if @experiments.nil?
107
+ experiment_events.reverse_each do |experiment_event|
108
+ id = experiment_event['data']['id']
109
+ variation = @experiments[id]
110
+ if variation.nil?
111
+ variation = Kameleoon::DataManager::AssignedVariation.new(id, experiment_event['data']['variationId'], Kameleoon::Configuration::RuleType::UNKNOWN, assignment_time: experiment_event['time'])
112
+ @experiments[id] = variation
113
+ end
114
+ end
115
+ end
116
+
117
+ def parse_conversions(conversion_events)
118
+ @conversions = [] if @conversions.nil?
119
+ conversion_events.each do |conversion_event|
120
+ data = conversion_event['data']
121
+ conversion = Conversion.new(data['goalId'], data['revenue'], data['negative'])
122
+ @conversions.push(conversion)
123
+ end
124
+ end
125
+
126
+ def parse_geolocation(geolocation_events)
127
+ data = geolocation_events[geolocation_events.size - 1]['data']
128
+ Geolocation.new(data['country'], data['region'], data['city'])
129
+ end
130
+
131
+ def parse_static_data(static_data_event)
132
+ return if @device != nil && @browser != nil && @operating_system != nil
133
+ data = static_data_event['data']
134
+ if @device == nil
135
+ device_type = data['deviceType']
136
+ @device = Device.new(device_type) if device_type != nil
137
+ end
138
+ if @browser == nil
139
+ browser_type = Kameleoon::BrowserType.from_name(data['browser'])
140
+ @browser = Browser.new(browser_type, data['browserVersion']) if browser_type != nil
141
+ end
142
+ if @operating_system == nil
143
+ operating_system_type = Kameleoon::OperatingSystemType.from_name(data['os'])
144
+ @operating_system = OperatingSystem.new(operating_system_type) if operating_system_type != nil
145
+ end
146
+ end
147
+
148
+ def conversions_single_objects
149
+ objects = []
150
+ unless @conversions.nil?
151
+ objects += @conversions
152
+ end
153
+ objects.push(@device) unless @device.nil?
154
+ objects.push(@browser) unless @browser.nil?
155
+ objects.push(@operating_system) unless @operating_system.nil?
156
+ objects.push(@geolocation) unless @geolocation.nil?
157
+ objects
158
+ end
159
+ end
160
+ end
161
+ end
162
+ 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)
@@ -20,12 +20,12 @@ module Kameleoon
20
20
  @log_func = log_func
21
21
  end
22
22
 
23
- def get_token
23
+ def get_token(timeout = nil)
24
24
  now = Time.new.to_i
25
25
  token = @cached_token
26
- return fetch_token if token.nil? || token.expired?(now)
26
+ return call_fetch_token(timeout) if token.nil? || token.expired?(now)
27
27
 
28
- Thread.new { fetch_token } if !@fetching && token.obsolete?(now)
28
+ run_fetch_token if !@fetching && token.obsolete?(now)
29
29
  token.value
30
30
  end
31
31
 
@@ -35,9 +35,26 @@ module Kameleoon
35
35
 
36
36
  private
37
37
 
38
- def fetch_token
38
+ def call_fetch_token(timeout)
39
39
  @fetching = true
40
- response_content = @network_manager.fetch_access_jwtoken(@client_id, @client_secret)
40
+ fetch_token(timeout)
41
+ rescue StandardError => e
42
+ @fetching = false
43
+ @log_func&.call("Failed to call access token fetching: #{e}")
44
+ nil
45
+ end
46
+
47
+ def run_fetch_token
48
+ # setting `@fetching` here to reduce the number of requests until new thread started
49
+ @fetching = true
50
+ Thread.new { fetch_token }
51
+ rescue StandardError => e
52
+ @fetching = false
53
+ @log_func&.call("Failed to run access token fetching: #{e}")
54
+ end
55
+
56
+ def fetch_token(timeout = nil)
57
+ response_content = @network_manager.fetch_access_jwtoken(@client_id, @client_secret, timeout)
41
58
  unless response_content
42
59
  @log_func&.call('Failed to fetch access JWT')
43
60
  return nil