kameleoon-client-ruby 3.3.0 → 3.4.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.
- checksums.yaml +4 -4
- data/lib/kameleoon/configuration/custom_data_info.rb +16 -8
- data/lib/kameleoon/configuration/data_file.rb +37 -15
- data/lib/kameleoon/configuration/feature_flag.rb +10 -0
- data/lib/kameleoon/configuration/rule.rb +4 -0
- data/lib/kameleoon/configuration/settings.rb +13 -8
- data/lib/kameleoon/configuration/variation_exposition.rb +4 -0
- data/lib/kameleoon/data/browser.rb +4 -0
- data/lib/kameleoon/data/conversion.rb +4 -0
- data/lib/kameleoon/data/cookie.rb +4 -0
- data/lib/kameleoon/data/custom_data.rb +11 -3
- data/lib/kameleoon/data/data.rb +30 -4
- data/lib/kameleoon/data/device.rb +4 -0
- data/lib/kameleoon/data/geolocation.rb +5 -0
- data/lib/kameleoon/data/kcs_heat.rb +4 -0
- data/lib/kameleoon/data/manager/assigned_variation.rb +5 -0
- data/lib/kameleoon/data/manager/data_array_storage.rb +7 -0
- data/lib/kameleoon/data/manager/data_map_storage.rb +7 -0
- data/lib/kameleoon/data/manager/page_view_visit.rb +4 -0
- data/lib/kameleoon/data/manager/visitor.rb +197 -73
- data/lib/kameleoon/data/manager/visitor_manager.rb +54 -17
- data/lib/kameleoon/data/mapping_identifier.rb +33 -0
- data/lib/kameleoon/data/operating_system.rb +4 -0
- data/lib/kameleoon/data/page_view.rb +6 -1
- data/lib/kameleoon/data/unique_identifier.rb +11 -0
- data/lib/kameleoon/data/user_agent.rb +4 -0
- data/lib/kameleoon/data/visitor_visits.rb +4 -0
- data/lib/kameleoon/hybrid/manager.rb +13 -4
- data/lib/kameleoon/kameleoon_client.rb +303 -148
- data/lib/kameleoon/kameleoon_client_config.rb +64 -17
- data/lib/kameleoon/kameleoon_client_factory.rb +15 -2
- data/lib/kameleoon/logging/default_logger.rb +20 -0
- data/lib/kameleoon/logging/kameleoon_logger.rb +77 -0
- data/lib/kameleoon/logging/logger.rb +12 -0
- data/lib/kameleoon/managers/data/data_manager.rb +36 -0
- data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +32 -15
- data/lib/kameleoon/managers/tracking/tracking_builder.rb +149 -0
- data/lib/kameleoon/managers/tracking/tracking_manager.rb +97 -0
- data/lib/kameleoon/managers/tracking/visitor_tracking_registry.rb +94 -0
- data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +22 -5
- data/lib/kameleoon/network/access_token_source.rb +46 -14
- data/lib/kameleoon/network/cookie/cookie_manager.rb +45 -7
- data/lib/kameleoon/network/net_provider.rb +2 -3
- data/lib/kameleoon/network/network_manager.rb +16 -21
- data/lib/kameleoon/network/request.rb +14 -3
- data/lib/kameleoon/network/response.rb +4 -0
- data/lib/kameleoon/network/url_provider.rb +4 -4
- data/lib/kameleoon/real_time/real_time_configuration_service.rb +10 -11
- data/lib/kameleoon/sdk_version.rb +31 -0
- data/lib/kameleoon/targeting/condition.rb +4 -2
- data/lib/kameleoon/targeting/conditions/browser_condition.rb +3 -3
- data/lib/kameleoon/targeting/conditions/cookie_condition.rb +10 -10
- data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +0 -1
- data/lib/kameleoon/targeting/conditions/number_condition.rb +4 -4
- data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +1 -2
- data/lib/kameleoon/targeting/conditions/sdk_language_condition.rb +2 -1
- data/lib/kameleoon/targeting/conditions/segment_condition.rb +3 -3
- data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -1
- data/lib/kameleoon/targeting/models.rb +0 -14
- data/lib/kameleoon/targeting/targeting_manager.rb +35 -7
- data/lib/kameleoon/targeting/tree_builder.rb +10 -5
- data/lib/kameleoon/types/remote_visitor_data_filter.rb +13 -0
- data/lib/kameleoon/types/variable.rb +4 -0
- data/lib/kameleoon/types/variation.rb +4 -0
- data/lib/kameleoon/utils.rb +18 -0
- data/lib/kameleoon/version.rb +1 -27
- 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,
|
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:
|
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
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
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
|
-
|
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,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
|
12
|
+
attr_reader :network_manager, :visitor_manger
|
11
13
|
|
12
|
-
def initialize(network_manager, visitor_manger
|
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
|
-
|
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
|
-
|
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,
|
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 =
|
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(
|
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 =
|
42
|
-
remote_visitor_data.mark_data_as_sent(@
|
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
|
-
|
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
|