kameleoon-client-ruby 3.1.1 → 3.2.0

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