kameleoon-client-ruby 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/configuration/custom_data_info.rb +16 -8
  3. data/lib/kameleoon/configuration/data_file.rb +37 -15
  4. data/lib/kameleoon/configuration/feature_flag.rb +10 -0
  5. data/lib/kameleoon/configuration/rule.rb +4 -0
  6. data/lib/kameleoon/configuration/settings.rb +13 -8
  7. data/lib/kameleoon/configuration/variation_exposition.rb +4 -0
  8. data/lib/kameleoon/data/browser.rb +4 -0
  9. data/lib/kameleoon/data/conversion.rb +4 -0
  10. data/lib/kameleoon/data/cookie.rb +4 -0
  11. data/lib/kameleoon/data/custom_data.rb +11 -3
  12. data/lib/kameleoon/data/data.rb +30 -4
  13. data/lib/kameleoon/data/device.rb +4 -0
  14. data/lib/kameleoon/data/geolocation.rb +5 -0
  15. data/lib/kameleoon/data/kcs_heat.rb +4 -0
  16. data/lib/kameleoon/data/manager/assigned_variation.rb +5 -0
  17. data/lib/kameleoon/data/manager/data_array_storage.rb +7 -0
  18. data/lib/kameleoon/data/manager/data_map_storage.rb +7 -0
  19. data/lib/kameleoon/data/manager/page_view_visit.rb +4 -0
  20. data/lib/kameleoon/data/manager/visitor.rb +197 -73
  21. data/lib/kameleoon/data/manager/visitor_manager.rb +54 -17
  22. data/lib/kameleoon/data/mapping_identifier.rb +33 -0
  23. data/lib/kameleoon/data/operating_system.rb +4 -0
  24. data/lib/kameleoon/data/page_view.rb +6 -1
  25. data/lib/kameleoon/data/unique_identifier.rb +11 -0
  26. data/lib/kameleoon/data/user_agent.rb +4 -0
  27. data/lib/kameleoon/data/visitor_visits.rb +4 -0
  28. data/lib/kameleoon/hybrid/manager.rb +13 -4
  29. data/lib/kameleoon/kameleoon_client.rb +303 -148
  30. data/lib/kameleoon/kameleoon_client_config.rb +64 -17
  31. data/lib/kameleoon/kameleoon_client_factory.rb +15 -2
  32. data/lib/kameleoon/logging/default_logger.rb +20 -0
  33. data/lib/kameleoon/logging/kameleoon_logger.rb +77 -0
  34. data/lib/kameleoon/logging/logger.rb +12 -0
  35. data/lib/kameleoon/managers/data/data_manager.rb +36 -0
  36. data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +32 -15
  37. data/lib/kameleoon/managers/tracking/tracking_builder.rb +149 -0
  38. data/lib/kameleoon/managers/tracking/tracking_manager.rb +97 -0
  39. data/lib/kameleoon/managers/tracking/visitor_tracking_registry.rb +94 -0
  40. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +22 -5
  41. data/lib/kameleoon/network/access_token_source.rb +46 -14
  42. data/lib/kameleoon/network/cookie/cookie_manager.rb +45 -7
  43. data/lib/kameleoon/network/net_provider.rb +2 -3
  44. data/lib/kameleoon/network/network_manager.rb +16 -21
  45. data/lib/kameleoon/network/request.rb +14 -3
  46. data/lib/kameleoon/network/response.rb +4 -0
  47. data/lib/kameleoon/network/url_provider.rb +4 -4
  48. data/lib/kameleoon/real_time/real_time_configuration_service.rb +10 -11
  49. data/lib/kameleoon/sdk_version.rb +31 -0
  50. data/lib/kameleoon/targeting/condition.rb +4 -2
  51. data/lib/kameleoon/targeting/conditions/browser_condition.rb +3 -3
  52. data/lib/kameleoon/targeting/conditions/cookie_condition.rb +10 -10
  53. data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +0 -1
  54. data/lib/kameleoon/targeting/conditions/number_condition.rb +4 -4
  55. data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +1 -2
  56. data/lib/kameleoon/targeting/conditions/sdk_language_condition.rb +2 -1
  57. data/lib/kameleoon/targeting/conditions/segment_condition.rb +3 -3
  58. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -1
  59. data/lib/kameleoon/targeting/models.rb +0 -14
  60. data/lib/kameleoon/targeting/targeting_manager.rb +35 -7
  61. data/lib/kameleoon/targeting/tree_builder.rb +10 -5
  62. data/lib/kameleoon/types/remote_visitor_data_filter.rb +13 -0
  63. data/lib/kameleoon/types/variable.rb +4 -0
  64. data/lib/kameleoon/types/variation.rb +4 -0
  65. data/lib/kameleoon/utils.rb +18 -0
  66. data/lib/kameleoon/version.rb +1 -27
  67. metadata +12 -2
@@ -1,29 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/logging/kameleoon_logger'
4
+
3
5
  module Kameleoon
4
6
  # KameleoonClient configuration which can be used instead of an external configuration file
5
7
  class KameleoonClientConfig
6
8
  DEFAULT_REFRESH_INTERVAL_MINUTES = 60
7
9
  DEFAULT_SESSION_DURATION_MINUTES = 30
8
10
  DEFAULT_TIMEOUT_MILLISECONDS = 10_000
11
+ DEFAULT_TRACKING_INTERVAL_MILLISECONDS = 1000
12
+ MIN_TRACKING_INTERVAL_MILLISECONDS = 300
13
+ MAX_TRACKING_INTERVAL_MILLISECONDS = 1000
9
14
 
10
15
  attr_reader :client_id, :client_secret, :refresh_interval_second, :session_duration_second,
11
- :default_timeout_millisecond, :environment, :top_level_domain, :verbose_mode
16
+ :default_timeout_millisecond, :tracking_interval_second, :environment, :top_level_domain,
17
+ :verbose_mode
18
+
19
+ def to_s
20
+ 'KameleoonClientConfig{' \
21
+ "client_id:'#{Utils::Strval.secret(@client_id)}'," \
22
+ "client_secret:'#{Utils::Strval.secret(@client_secret)}'," \
23
+ "refresh_interval_second:#{@refresh_interval_second}," \
24
+ "session_duration_second:#{@session_duration_second}," \
25
+ "environment:'#{@environment}'," \
26
+ "default_timeout_millisecond:#{@default_timeout_millisecond}," \
27
+ "top_level_domain:'#{@top_level_domain}'," \
28
+ "verbose_mode:#{verbose_mode}" \
29
+ '}'
30
+ end
12
31
 
32
+ # verbose_mode is DEPRECATED. Please use `KameleoonLogger.log_level` instead.
13
33
  def initialize(
14
34
  client_id,
15
35
  client_secret,
16
36
  refresh_interval_minute: DEFAULT_REFRESH_INTERVAL_MINUTES,
17
37
  session_duration_minute: DEFAULT_SESSION_DURATION_MINUTES,
18
38
  default_timeout_millisecond: DEFAULT_TIMEOUT_MILLISECONDS,
39
+ tracking_interval_millisecond: DEFAULT_TRACKING_INTERVAL_MILLISECONDS,
19
40
  environment: nil,
20
41
  top_level_domain: nil,
21
- verbose_mode: false
42
+ verbose_mode: nil
22
43
  )
23
44
  raise Exception::ConfigCredentialsInvalid, 'Client ID is not specified' if client_id&.empty? != false
24
45
  raise Exception::ConfigCredentialsInvalid, 'Client secret is not specified' if client_secret&.empty? != false
25
46
 
26
- @verbose_mode = verbose_mode || false
47
+ unless verbose_mode.nil?
48
+ Logging::KameleoonLogger.warning(
49
+ '[DEPRECATION] `verbose_mode` is deprecated. Please use `KameleoonLogger.log_level` instead.'
50
+ )
51
+ end
52
+
53
+ @verbose_mode = verbose_mode
27
54
 
28
55
  @client_id = client_id
29
56
  @client_secret = client_secret
@@ -31,8 +58,10 @@ module Kameleoon
31
58
  if refresh_interval_minute.nil?
32
59
  refresh_interval_minute = DEFAULT_REFRESH_INTERVAL_MINUTES
33
60
  elsif refresh_interval_minute <= 0
34
- log('Configuration refresh interval must have positive value. ' \
35
- "Default refresh interval (#{DEFAULT_REFRESH_INTERVAL_MINUTES} minutes) is applied.")
61
+ Logging::KameleoonLogger.warning(lambda {
62
+ 'Configuration refresh interval must have positive value. ' \
63
+ "Default refresh interval (#{DEFAULT_REFRESH_INTERVAL_MINUTES} minutes) was applied."
64
+ })
36
65
  refresh_interval_minute = DEFAULT_REFRESH_INTERVAL_MINUTES
37
66
  end
38
67
  @refresh_interval_second = refresh_interval_minute * 60
@@ -40,8 +69,10 @@ module Kameleoon
40
69
  if session_duration_minute.nil?
41
70
  session_duration_minute = DEFAULT_SESSION_DURATION_MINUTES
42
71
  elsif session_duration_minute <= 0
43
- log('Session duration must have positive value. ' \
44
- "Default session duration (#{DEFAULT_SESSION_DURATION_MINUTES} minutes) is applied.")
72
+ Logging::KameleoonLogger.warning(lambda {
73
+ 'Session duration must have positive value. ' \
74
+ "Default session duration (#{DEFAULT_SESSION_DURATION_MINUTES} minutes) was applied."
75
+ })
45
76
  session_duration_minute = DEFAULT_SESSION_DURATION_MINUTES
46
77
  end
47
78
  @session_duration_second = session_duration_minute * 60
@@ -49,17 +80,38 @@ module Kameleoon
49
80
  if default_timeout_millisecond.nil?
50
81
  @default_timeout_millisecond = DEFAULT_TIMEOUT_MILLISECONDS
51
82
  elsif default_timeout_millisecond <= 0
52
- log('Default timeout must have positive value. ' \
53
- "Default timeout (#{DEFAULT_TIMEOUT_MILLISECONDS} ms) is applied.")
83
+ Logging::KameleoonLogger.warning(lambda {
84
+ 'Default timeout must have positive value. ' \
85
+ "Default timeout (#{DEFAULT_TIMEOUT_MILLISECONDS} ms) was applied."
86
+ })
54
87
  @default_timeout_millisecond = DEFAULT_TIMEOUT_MILLISECONDS
55
88
  else
56
89
  @default_timeout_millisecond = default_timeout_millisecond
57
90
  end
58
91
 
92
+ if tracking_interval_millisecond.nil?
93
+ tracking_interval_millisecond = DEFAULT_TRACKING_INTERVAL_MILLISECONDS
94
+ elsif tracking_interval_millisecond < MIN_TRACKING_INTERVAL_MILLISECONDS
95
+ Logging::KameleoonLogger.warning(lambda {
96
+ 'Tracking interval must not be shorter than ' \
97
+ "#{MIN_TRACKING_INTERVAL_MILLISECONDS} ms. Minimum possible interval was applied."
98
+ })
99
+ tracking_interval_millisecond = MIN_TRACKING_INTERVAL_MILLISECONDS
100
+ elsif tracking_interval_millisecond > MAX_TRACKING_INTERVAL_MILLISECONDS
101
+ Logging::KameleoonLogger.warning(lambda {
102
+ 'Tracking interval must not be longer than ' \
103
+ "#{MAX_TRACKING_INTERVAL_MILLISECONDS} ms. Maximum possible interval was applied."
104
+ })
105
+ tracking_interval_millisecond = MAX_TRACKING_INTERVAL_MILLISECONDS
106
+ end
107
+ @tracking_interval_second = tracking_interval_millisecond / 1000.0
108
+
59
109
  @environment = environment
60
110
 
61
111
  if top_level_domain.nil?
62
- log('Setting top level domain is strictly recommended, otherwise you may have problems when using subdomains.')
112
+ Logging::KameleoonLogger.warning(
113
+ 'Setting top level domain is strictly recommended, otherwise you may have problems when using subdomains.'
114
+ )
63
115
  end
64
116
  @top_level_domain = top_level_domain
65
117
  end
@@ -67,7 +119,7 @@ module Kameleoon
67
119
  def self.read_from_yaml(path)
68
120
  yaml = YAML.load_file(path) if File.exist?(path)
69
121
  if yaml.nil?
70
- warn "Kameleoon SDK: Configuration file with path #{path} does not exist"
122
+ Logging::KameleoonLogger.warning(-> { "Configuration file with path '#{path}' does not exist" })
71
123
  yaml = {}
72
124
  end
73
125
  KameleoonClientConfig.new(
@@ -76,16 +128,11 @@ module Kameleoon
76
128
  refresh_interval_minute: yaml['refresh_interval_minute'],
77
129
  session_duration_minute: yaml['session_duration_minute'],
78
130
  default_timeout_millisecond: yaml['default_timeout_millisecond'],
131
+ tracking_interval_millisecond: yaml['tracking_interval_millisecond'],
79
132
  environment: yaml['environment'],
80
133
  top_level_domain: yaml['top_level_domain'],
81
134
  verbose_mode: yaml['verbose_mode']
82
135
  )
83
136
  end
84
-
85
- private
86
-
87
- def log(text)
88
- print "Kameleoon SDK Log: #{text}\n" if @verbose_mode
89
- end
90
137
  end
91
138
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'concurrent'
4
+ require 'kameleoon/logging/kameleoon_logger'
4
5
  require 'kameleoon/kameleoon_client'
5
6
  require 'kameleoon/kameleoon_client_config'
6
7
 
@@ -12,25 +13,37 @@ module Kameleoon
12
13
  @clients = Concurrent::Map.new
13
14
 
14
15
  def self.create(site_code, config: nil, config_path: CONFIG_PATH)
16
+ Logging::KameleoonLogger.info(
17
+ "CALL: KameleoonClientFactory.create(site_code: '%s', config: %s, config_path: '%s')",
18
+ site_code, config, config_path
19
+ )
15
20
  unless config.is_a?(KameleoonClientConfig)
16
21
  config_path = CONFIG_PATH unless config_path.is_a?(String)
17
22
  config = KameleoonClientConfig.read_from_yaml(config_path)
18
23
  end
19
24
  key = get_client_key(site_code, config.environment)
20
- @clients.compute_if_absent(key) do
25
+ client = @clients.compute_if_absent(key) do
21
26
  client = KameleoonClient.new(site_code, config)
22
- client.send(:log, "Client created with site code: #{site_code}")
23
27
  client.send(:fetch_configuration_initially)
24
28
  client
25
29
  end
30
+ Logging::KameleoonLogger.info(
31
+ "RETURN: KameleoonClientFactory.create(site_code: '%s', config: %s, config_path: '%s') -> (client)",
32
+ site_code, config, config_path
33
+ )
34
+ client
26
35
  end
27
36
 
28
37
  def self.forget(site_code, environment = nil)
38
+ Logging::KameleoonLogger.info("CALL: KameleoonClientFactory.forget(site_code: '%s', environment: '%s')",
39
+ site_code, environment)
29
40
  key = get_client_key(site_code, environment)
30
41
  @clients.compute_if_present(key) do |client|
31
42
  client.send(:dispose)
32
43
  nil
33
44
  end
45
+ Logging::KameleoonLogger.info("RETURN: KameleoonClientFactory.forget(site_code: '%s', environment: '%s')",
46
+ site_code, environment)
34
47
  end
35
48
 
36
49
  private_class_method
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/logging/logger'
4
+
5
+ module Kameleoon
6
+ module Logging
7
+ # A default implementation of a logger that prints log messages to the console.
8
+ # This logger implements the Logger interface.
9
+ class DefaultLogger < Logger
10
+
11
+ # Logs a message at the specified log level.
12
+ #
13
+ # @param level [LogLevel] the log level
14
+ # @param message [String] the log message
15
+ def log(level, message)
16
+ puts message
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/logging/default_logger'
4
+
5
+ module Kameleoon
6
+ module Logging
7
+ module LogLevel
8
+ NONE = 0
9
+ ERROR = 1
10
+ WARNING = 2
11
+ INFO = 3
12
+ DEBUG = 4
13
+
14
+ def self.name_from_level(log_level)
15
+ case log_level
16
+ when LogLevel::NONE
17
+ 'NONE'
18
+ when LogLevel::ERROR
19
+ 'ERROR'
20
+ when LogLevel::WARNING
21
+ 'WARNING'
22
+ when LogLevel::INFO
23
+ 'INFO'
24
+ when LogLevel::DEBUG
25
+ 'DEBUG'
26
+ end
27
+ end
28
+ end
29
+
30
+ module KameleoonLogger
31
+ extend self
32
+
33
+ @logger = DefaultLogger.new
34
+ @log_level = LogLevel::WARNING
35
+
36
+ attr_accessor :logger, :log_level
37
+
38
+ def log(level, data, *args)
39
+ return unless check_level(level)
40
+
41
+ if data.class.method_defined?(:call)
42
+ message = data.call
43
+ else
44
+ message = args.empty? ? data : data % args
45
+ end
46
+
47
+ write_message(level, message)
48
+ end
49
+
50
+ def info(data, *args)
51
+ log(LogLevel::INFO, data, *args)
52
+ end
53
+
54
+ def error(data, *args)
55
+ log(LogLevel::ERROR, data, *args)
56
+ end
57
+
58
+ def warning(data, *args)
59
+ log(LogLevel::WARNING, data, *args)
60
+ end
61
+
62
+ def debug(data, *args)
63
+ log(LogLevel::DEBUG, data, *args)
64
+ end
65
+
66
+ private
67
+
68
+ def check_level(level)
69
+ level <= @log_level && level != LogLevel::NONE
70
+ end
71
+
72
+ def write_message(level, message)
73
+ @logger.log(level, "Kameleoon [#{LogLevel.name_from_level(level)}]: #{message}")
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module Logging
5
+ class Logger
6
+
7
+ def log(level, message)
8
+ raise NotImplementedError, 'Subclasses must implement log(level, message)'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ module Managers
5
+ module Data
6
+ class DataManager
7
+ def initialize(data_file)
8
+ self.data_file = data_file
9
+ end
10
+
11
+ def data_file
12
+ @container.data_file
13
+ end
14
+
15
+ def data_file=(value)
16
+ @container = Container.new(value)
17
+ end
18
+
19
+ def consent_required?
20
+ @container.is_consent_required
21
+ end
22
+
23
+ class Container
24
+ attr_reader :data_file, :is_consent_required
25
+
26
+ def initialize(data_file)
27
+ @data_file = data_file
28
+ # Regarding GDPR policy we should set visitorCode if legal consent isn't required or we have at
29
+ # least one Targeted Delivery rule in datafile
30
+ @is_consent_required = data_file.settings.is_consent_required && !data_file.has_any_targeted_delivery_rule
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,54 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/logging/kameleoon_logger'
3
4
  require 'kameleoon/managers/remote_data/remote_visitor_data'
4
5
  require 'kameleoon/types/remote_visitor_data_filter'
6
+ require 'kameleoon/utils'
5
7
 
6
8
  module Kameleoon
7
9
  module Managers
8
10
  module RemoteData
9
11
  class RemoteDataManager
10
- attr_reader :network_manager, :visitor_manger, :log_func
12
+ attr_reader :network_manager, :visitor_manger
11
13
 
12
- def initialize(network_manager, visitor_manger, log_func = nil)
14
+ def initialize(data_manager, network_manager, visitor_manger)
15
+ Logging::KameleoonLogger.debug('CALL: RemoteDataManager.new(data_manager, networkManager, visitorManager)')
16
+ @data_manager = data_manager
13
17
  @network_manager = network_manager
14
18
  @visitor_manger = visitor_manger
15
- @log_func = log_func
19
+ Logging::KameleoonLogger.debug('RETURN: RemoteDataManager.new(data_manager, networkManager, visitorManager)')
16
20
  end
17
21
 
18
22
  def get_data(key, timeout)
23
+ Logging::KameleoonLogger.debug("CALL: RemoteDataManager.get_data(key: '%s', timeout: %s)", key, timeout)
19
24
  response = @network_manager.get_remote_data(key, timeout)
20
- JSON.parse(response) if response
25
+ data = JSON.parse(response) if response
26
+ Logging::KameleoonLogger.debug(
27
+ "RETURN: RemoteDataManager.get_data(key: '%s', timeout: %s) -> (remote_data: %s)",
28
+ key, timeout, data
29
+ )
30
+ data
21
31
  rescue StandardError => e
22
- log("Parsing of visitor data of '#{key}' failed: #{e}")
32
+ Logging::KameleoonLogger.error("Parsing of remote data of '#{key}' failed: #{e}")
23
33
  raise
24
34
  end
25
35
 
26
- def get_visitor_data(visitor_code, add_data, filter = nil, is_unique_identifier = false, timeout = nil)
36
+ def get_visitor_data(visitor_code, add_data, filter = nil, timeout = nil)
37
+ Logging::KameleoonLogger.debug(
38
+ "CALL: RemoteDataManager.get_visitor_data(visitor_code: '%s', add_data: %s, filter: %s, timeout: %s)",
39
+ visitor_code, add_data, filter, timeout
40
+ )
27
41
  Utils::VisitorCode.validate(visitor_code)
28
- filter = Kameleoon::Types::RemoteVisitorDataFilter.new unless filter.is_a?(Kameleoon::Types::RemoteVisitorDataFilter)
42
+ filter = Types::RemoteVisitorDataFilter.new unless filter.is_a?(Types::RemoteVisitorDataFilter)
43
+ is_unique_identifier = @visitor_manger.get_visitor(visitor_code)&.is_unique_identifier || false
29
44
  response = @network_manager.get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout)
30
45
  (data_to_add, data_to_return) = parse_custom_data_array(visitor_code, response)
31
46
  if add_data && !data_to_add.empty?
47
+ # Cannot use `VisitorManager.add_data` because it could use remote visitor data for mapping.
32
48
  visitor = @visitor_manger.get_or_create_visitor(visitor_code)
33
- visitor.add_data(@log_func, *data_to_add, overwrite: false)
49
+ visitor.add_data(*data_to_add, overwrite: false)
34
50
  end
51
+ Logging::KameleoonLogger.debug(
52
+ "RETURN: RemoteDataManager.get_visitor_data(visitor_code: '%s', add_data: %s, filter: %s," \
53
+ ' timeout: %s) -> (visitor_data: %s)',
54
+ visitor_code, add_data, filter, timeout, data_to_return
55
+ )
35
56
  data_to_return
36
57
  end
37
58
 
38
59
  ##
39
60
  # helper method used by `get_remote_visitor_data`
40
61
  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)
62
+ remote_visitor_data = RemoteVisitorData.new(JSON.parse(response))
63
+ remote_visitor_data.mark_data_as_sent(@data_manager.data_file.custom_data_info)
43
64
  [remote_visitor_data.collect_data_to_add, remote_visitor_data.collect_data_to_return]
44
65
  rescue StandardError => e
45
- log("Parsing of visitor data of '#{visitor_code}' failed: #{e}")
66
+ Logging::KameleoonLogger.error("Parsing of remote visitor data of '#{visitor_code}' failed: #{e}")
46
67
  raise
47
68
  end
48
-
49
- def log(text)
50
- @log_func&.call(text)
51
- end
52
69
  end
53
70
  end
54
71
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/custom_data'
4
+ require 'kameleoon/logging/kameleoon_logger'
5
+ require 'kameleoon/network/activity_event'
6
+ require 'kameleoon/network/uri_helper'
7
+
8
+ module Kameleoon
9
+ module Managers
10
+ module Tracking
11
+ class TrackingBuilder
12
+ attr_reader :visitor_codes_to_send, :visitor_codes_to_keep, :tracking_lines, :unsent_visitor_data
13
+
14
+ def initialize(visitor_codes, data_file, visitor_manager, request_size_limit)
15
+ @visitor_codes = visitor_codes
16
+ @data_file = data_file
17
+ @visitor_manager = visitor_manager
18
+ @request_size_limit = request_size_limit
19
+ @built = false
20
+ @total_size = 0
21
+ # result
22
+ @visitor_codes_to_send = []
23
+ @visitor_codes_to_keep = []
24
+ @tracking_lines = []
25
+ @unsent_visitor_data = []
26
+ end
27
+
28
+ # Not thread-safe
29
+ def build
30
+ return if @built
31
+
32
+ @visitor_codes.each do |visitor_code|
33
+ if @total_size <= @request_size_limit
34
+ visitor = @visitor_manager.get_visitor(visitor_code)
35
+ is_consent_given = consent_given?(visitor)
36
+ data = collect_tracking_data(visitor_code, visitor, is_consent_given)
37
+ if !data.empty?
38
+ log_visitor_track_sending(visitor_code, is_consent_given, data)
39
+ @visitor_codes_to_send.push(visitor_code)
40
+ @unsent_visitor_data.concat(data)
41
+ else
42
+ log_visitor_track_no_data(visitor_code, is_consent_given)
43
+ end
44
+ else
45
+ @visitor_codes_to_keep.push(visitor_code)
46
+ end
47
+ end
48
+ @built = true
49
+ end
50
+
51
+ private
52
+
53
+ def log_visitor_track_sending(visitor_code, is_consent_given, data)
54
+ Logging::KameleoonLogger.debug(
55
+ "Sending tracking request for unsent data %s of visitor '%s' with given (or not required) consent %s",
56
+ data, visitor_code, is_consent_given
57
+ )
58
+ end
59
+
60
+ def log_visitor_track_no_data(visitor_code, is_consent_given)
61
+ Logging::KameleoonLogger.debug(
62
+ "No data to send for visitor '%s' with given (or not required) consent %s",
63
+ visitor_code, is_consent_given
64
+ )
65
+ end
66
+
67
+ def consent_given?(visitor)
68
+ !@data_file.settings.is_consent_required || visitor&.legal_consent
69
+ end
70
+
71
+ def collect_tracking_data(visitor_code, visitor, is_consent_given)
72
+ use_mapping_value, visitor = create_self_visitor_link_if_required(visitor_code, visitor)
73
+ Logging::KameleoonLogger.info(
74
+ "'%s' was used as a %s for visitor data tracking.",
75
+ visitor_code, (use_mapping_value ? 'mapping value' : 'visitor code')
76
+ )
77
+ unsent_data = get_unsent_visitor_data(visitor, is_consent_given)
78
+ collect_tracking_lines(visitor_code, visitor, unsent_data, use_mapping_value)
79
+ unsent_data
80
+ end
81
+
82
+ def create_self_visitor_link_if_required(visitor_code, visitor)
83
+ is_mapped = !visitor&.mapping_identifier.nil?
84
+ is_unique_identifier = visitor&.is_unique_identifier
85
+ # need to find if anonymous visitor is behind unique (anonym doesn't exist if MappingIdentifier == null)
86
+ if is_unique_identifier && !is_mapped
87
+ # We haven't anonymous behind, in this case we should create "fake" anonymous with id == visitorCode
88
+ # and link it with with mapping value == visitorCode (like we do as we have real anonymous visitor)
89
+ visitor = @visitor_manager.add_data(
90
+ visitor_code, CustomData.new(@data_file.custom_data_info.mapping_identifier_index, visitor_code)
91
+ )
92
+ end
93
+ use_mapping_value = is_unique_identifier && (visitor_code != visitor&.mapping_identifier)
94
+ [use_mapping_value, visitor]
95
+ end
96
+
97
+ def get_unsent_visitor_data(visitor, is_consent_given)
98
+ unsent_data = []
99
+ unless visitor.nil?
100
+ if is_consent_given
101
+ visitor.enumerate_sendable_data do |data|
102
+ unsent_data.push(data) if data.unsent
103
+ next true
104
+ end
105
+ else
106
+ visitor.conversions.enumerate do |c|
107
+ unsent_data.push(c) if c.unsent
108
+ next true
109
+ end
110
+ if @data_file.has_any_targeted_delivery_rule
111
+ visitor.variations.enumerate do |av|
112
+ unsent_data.push(av) if av.unsent && (av.rule_type == Configuration::RuleType::TARGETED_DELIVERY)
113
+ next true
114
+ end
115
+ end
116
+ end
117
+ end
118
+ unsent_data.push(Network::ActivityEvent.new) if unsent_data.empty? && is_consent_given
119
+ unsent_data
120
+ end
121
+
122
+ def collect_tracking_lines(visitor_code, visitor, unsent_data, use_mapping_value)
123
+ visitor_code_param = Network::UriHelper.encode_query(
124
+ { (use_mapping_value ? :mappingValue : :visitorCode) => visitor_code }
125
+ )
126
+ user_agent = visitor&.user_agent
127
+ unsent_data.each do |data|
128
+ line = data.obtain_full_post_text_line
129
+ next if line.empty?
130
+
131
+ line = add_line_params(line, visitor_code_param, user_agent)
132
+ @tracking_lines.push(line)
133
+ @total_size += line.size
134
+ user_agent = nil
135
+ end
136
+ end
137
+
138
+ def add_line_params(tracking_line, visitor_code_param, user_agent)
139
+ tracking_line += "&#{visitor_code_param}"
140
+ unless user_agent.nil?
141
+ user_agent_param = Network::UriHelper.encode_query({ userAgent: user_agent })
142
+ tracking_line += "&#{user_agent_param}"
143
+ end
144
+ tracking_line
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end