kameleoon-client-ruby 3.9.0 → 3.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d0ea98aa7ca7ea1585f83f6bd231aa577a774124b4332d18ca4e2c2649bfbc2
4
- data.tar.gz: ec43717ed1dd3a61828a9c062f10040a7e214e070f30f6deb2a76bec9d60fb76
3
+ metadata.gz: d70f414ac8fb37aa97f11f9a01008c602e828efe9305399964910914eff43050
4
+ data.tar.gz: 52ece8845cf02e17198268fd9b5b8e3275c86a11bbf01acc7e68362a80a64e46
5
5
  SHA512:
6
- metadata.gz: bec6bd1d823d247b5b1e561e621f454e4d451bec12fb1312dc2a9f348f1e2ccbcc884e6927bae254da107e6314e4d91acad9bb1ef67012d96b62a34253f2978f
7
- data.tar.gz: d5f5c2c4d85b5d2f706d35fc77f3bc5ce8b6e24ce56413e5d9086fd95aae0d8adf1bbe37a6b5d041f389575db0c8c3815e005a78a7a9d45f255a9325b4f11c46
6
+ metadata.gz: d62ef28c65650b491fd47beebb8dbdcee803cf78faacec4c998a6e02681cbf0f0aeb032db78637e94703eeb88de7821eb1fe84b4fd943ee65328cd87942332c9
7
+ data.tar.gz: 7e980fbb43d05dcdefceb257d003f7846e2bb131f0814216dcec56ef3273b35fe65ffac0cabe4cff1cbb0da1cb6095f41ca63c05241d42d972a844094e0d83d5
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ class CBScores
5
+ # keys = experiment IDs / values = list of variation IDs ordered descending
6
+ # by score (there may be several variation ids with same score)
7
+ attr_reader :values
8
+
9
+ def initialize(cbs_map)
10
+ values = cbs_map.transform_values { |cbs_value| extract_var_ids(cbs_value) }
11
+ values.freeze
12
+ @values = values
13
+ end
14
+
15
+ def to_s
16
+ str_values = @values.transform_values { |vgs| vgs.map(&:to_s) }
17
+ "CBScores{values:#{str_values}}"
18
+ end
19
+
20
+ class ScoredVarId
21
+ attr_reader :variation_id, :score
22
+
23
+ def initialize(variation_id, score)
24
+ @variation_id = variation_id
25
+ @score = score
26
+ end
27
+ end
28
+
29
+ class VarGroup
30
+ attr_reader :ids
31
+
32
+ def initialize(ids)
33
+ ids.sort!
34
+ ids.freeze
35
+ @ids = ids
36
+ end
37
+
38
+ def to_s
39
+ "VarGroup{ids:#{@ids}}"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def extract_var_ids(scores)
46
+ grouped = {}
47
+ scores.each do |score|
48
+ glist = grouped[score.score]
49
+ if glist
50
+ glist.push(score.variation_id)
51
+ else
52
+ grouped[score.score] = [score.variation_id]
53
+ end
54
+ end
55
+ grouped.sort_by { |score, _| -score }.collect { |_, glist| VarGroup.new(glist) }
56
+ end
57
+ end
58
+ end
@@ -1,26 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'kameleoon/data/custom_data'
4
5
  require 'kameleoon/network/uri_helper'
6
+ require 'kameleoon/utils'
5
7
  require_relative 'data'
6
8
 
7
9
  module Kameleoon
8
10
  # Conversion class uses for tracking conversion
9
11
  class Conversion < DuplicationSafeData
10
- attr_reader :goal_id, :revenue, :negative
12
+ attr_reader :goal_id, :revenue, :negative, :metadata
11
13
 
12
14
  def to_s
13
- "Conversion{goal_id:#{@goal_id},revenue:#{@revenue},negative:#{@negative}}"
15
+ "Conversion{goal_id:#{@goal_id},revenue:#{@revenue},negative:#{@negative}," \
16
+ "metadata:#{Utils::Strval.obj_to_s(@metadata)}}"
14
17
  end
15
18
 
16
19
  # @param [Integer] goal_id Id of the goal associated to the conversion
17
20
  # @param [Float] revenue Optional field - Revenue associated to the conversion.
18
21
  # @param [Boolean] negative Optional field - If the revenue is negative. By default it's positive.
19
- def initialize(goal_id, revenue = 0.0, negative = false)
22
+ # @param [Array] metadata Optional field - Metadata of the conversion. Array of CustomData objects.
23
+ def initialize(goal_id, revenue = 0.0, negative = false, metadata: nil)
20
24
  super(DataType::CONVERSION)
21
25
  @goal_id = goal_id
22
26
  @revenue = revenue || 0.0
23
27
  @negative = negative || false
28
+ @metadata = metadata
24
29
  end
25
30
 
26
31
  def obtain_full_post_text_line
@@ -31,7 +36,22 @@ module Kameleoon
31
36
  negative: @negative,
32
37
  nonce: nonce
33
38
  }
39
+ params[:metadata] = encode_metadata if @metadata&.length&.positive?
34
40
  Kameleoon::Network::UriHelper.encode_query(params)
35
41
  end
42
+
43
+ private
44
+
45
+ def encode_metadata
46
+ metadata = {}
47
+ added_indices = Set[]
48
+ @metadata&.each do |mcd|
49
+ next if !mcd.is_a?(CustomData) || added_indices.include?(mcd.id)
50
+
51
+ metadata[mcd.id] = mcd.values
52
+ added_indices.add(mcd.id)
53
+ end
54
+ JSON.generate(metadata)
55
+ end
36
56
  end
37
57
  end
@@ -38,10 +38,6 @@ module Kameleoon
38
38
  @values = args
39
39
  end
40
40
 
41
- if @values.empty?
42
- Logging::KameleoonLogger.error('Created a custom data %s with no values. It will not be tracked.', @id)
43
- end
44
-
45
41
  return if @id.is_a?(Integer)
46
42
 
47
43
  Logging::KameleoonLogger.warning("CustomData field 'id' must be of 'Integer' type")
@@ -50,8 +46,6 @@ module Kameleoon
50
46
  # rubocop:enable Metrics/MethodLength
51
47
 
52
48
  def obtain_full_post_text_line
53
- return '' if @values.empty?
54
-
55
49
  str_values = JSON.generate(Hash[@values.collect { |k| [k, 1] }])
56
50
  params = {
57
51
  eventType: 'customData',
@@ -47,4 +47,3 @@ module Kameleoon
47
47
  end
48
48
  end
49
49
  end
50
-
@@ -8,9 +8,9 @@ module Kameleoon
8
8
  @values = values
9
9
  @values.freeze
10
10
  end
11
- end
12
11
 
13
- def to_s
14
- "KcsHeat{values:#{@values}}"
12
+ def to_s
13
+ "KcsHeat{values:#{@values}}"
14
+ end
15
15
  end
16
16
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'concurrent'
4
4
  require 'kameleoon/data/browser'
5
+ require 'kameleoon/data/cbscores'
5
6
  require 'kameleoon/data/conversion'
6
7
  require 'kameleoon/data/custom_data'
7
8
  require 'kameleoon/data/device'
@@ -106,6 +107,12 @@ module Kameleoon
106
107
  kcs_heat
107
108
  end
108
109
 
110
+ def cbscores
111
+ cbs = @data.cbscores
112
+ Logging::KameleoonLogger.debug('CALL/RETURN: Visitor.cbscores -> (cbs: %s)', cbs)
113
+ cbs
114
+ end
115
+
109
116
  def visitor_visits
110
117
  visitor_visits = @data.visitor_visits
111
118
  Logging::KameleoonLogger.debug('CALL/RETURN: Visitor.visitor_visits -> (visitor_visits: %s)', visitor_visits)
@@ -242,6 +249,8 @@ module Kameleoon
242
249
  @data.set_geolocation(data, overwrite)
243
250
  when KcsHeat
244
251
  @data.kcs_heat = data
252
+ when CBScores
253
+ @data.set_cbscores(data, overwrite)
245
254
  when VisitorVisits
246
255
  @data.visitor_visits = data
247
256
  when UniqueIdentifier
@@ -267,7 +276,7 @@ module Kameleoon
267
276
 
268
277
  class VisitorData
269
278
  attr_reader :mutex, :device, :browser, :geolocation, :operating_system
270
- attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :visitor_visits,
279
+ attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores, :visitor_visits,
271
280
  :mapping_identifier, :forced_variations, :simulated_variations
272
281
 
273
282
  def initialize
@@ -398,6 +407,10 @@ module Kameleoon
398
407
  @operating_system = operating_system if overwrite || @operating_system.nil?
399
408
  end
400
409
 
410
+ def set_cbscores(cbs, overwrite)
411
+ @cbscores = cbs if overwrite || @cbscores.nil?
412
+ end
413
+
401
414
  def add_forced_feature_variation(forced_variation)
402
415
  @simulated_variations ||= {}
403
416
  @simulated_variations[forced_variation.feature_key] = forced_variation
@@ -67,7 +67,7 @@ module Kameleoon
67
67
  config.environment,
68
68
  config.default_timeout_millisecond,
69
69
  Network::AccessTokenSourceFactory.new(config.client_id, config.client_secret),
70
- Network::UrlProvider.new(site_code)
70
+ Network::UrlProvider.new(site_code, config.network_domain)
71
71
  )
72
72
  @tracking_manager = Managers::Tracking::TrackingManager.new(
73
73
  @data_manager, @network_manager, @visitor_manager, config.tracking_interval_second, @scheduler
@@ -186,21 +186,29 @@ module Kameleoon
186
186
  # @param [Float] revenue Optional - Revenue of the conversion.
187
187
  # @param [Bool] is_unique_identifier(DEPRECATED) Parameter that specifies whether the visitorCode is a unique
188
188
  # identifier. This field is optional.
189
+ # @param [Bool] negative Defines if the revenue is positive or negative.
190
+ # This field is optional (`false` by default).
191
+ # @param [Array] metadata Metadata of the conversion. This field is optional.
189
192
  #
190
193
  # @raise [Kameleoon::Exception::VisitorCodeInvalid] If the visitor code is empty or longer than 255 chars
191
194
  #
192
- def track_conversion(visitor_code, goal_id, revenue = 0.0, is_unique_identifier: nil)
195
+ def track_conversion(
196
+ visitor_code, goal_id, revenue = 0.0,
197
+ is_unique_identifier: nil, negative: false, metadata: nil
198
+ )
193
199
  Logging::KameleoonLogger.info(
194
- "CALL: KameleoonClient.track_conversion(visitor_code: '%s', goal_id: %s, revenue: %s, is_unique_identifier: %s)",
195
- visitor_code, goal_id, revenue, is_unique_identifier
200
+ "CALL: KameleoonClient.track_conversion(visitor_code: '%s', goal_id: %s, revenue: %s, " \
201
+ 'is_unique_identifier: %s, negative: %s, metadata: %s)',
202
+ visitor_code, goal_id, revenue, is_unique_identifier, negative, metadata
196
203
  )
197
204
  Utils::VisitorCode.validate(visitor_code)
198
205
  set_unique_identifier(visitor_code, is_unique_identifier) unless is_unique_identifier.nil?
199
- add_data(visitor_code, Conversion.new(goal_id, revenue))
206
+ add_data(visitor_code, Conversion.new(goal_id, revenue, negative, metadata: metadata))
200
207
  @tracking_manager.add_visitor_code(visitor_code)
201
208
  Logging::KameleoonLogger.info(
202
- "RETURN: KameleoonClient.track_conversion(visitor_code: '%s', goal_id: %s, revenue: %s, is_unique_identifier: %s)",
203
- visitor_code, goal_id, revenue, is_unique_identifier
209
+ "RETURN: KameleoonClient.track_conversion(visitor_code: '%s', goal_id: %s, revenue: %s, " \
210
+ 'is_unique_identifier: %s, negative: %s, metadata: %s)',
211
+ visitor_code, goal_id, revenue, is_unique_identifier, negative, metadata
204
212
  )
205
213
  end
206
214
 
@@ -1032,6 +1040,42 @@ module Kameleoon
1032
1040
  visitor&.mapping_identifier || visitor_code
1033
1041
  end
1034
1042
 
1043
+ def evaluate_cbscores(visitor, visitor_code, rule)
1044
+ return nil if visitor&.cbscores.nil?
1045
+
1046
+ Logging::KameleoonLogger.debug(
1047
+ "CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s)", visitor_code, rule
1048
+ )
1049
+ eval_exp = nil
1050
+ var_id_group_by_scores = visitor.cbscores.values[rule.experiment.id]
1051
+ if var_id_group_by_scores
1052
+ var_by_exp_in_cbs = nil
1053
+ var_id_group_by_scores.each do |var_group|
1054
+ var_by_exp_in_cbs = rule.experiment.variations_by_exposition.filter do |var_by_exp|
1055
+ var_group.ids.include?(var_by_exp.variation_id)
1056
+ end
1057
+ break unless var_by_exp_in_cbs.empty?
1058
+ end
1059
+ if (var_by_exp_in_cbs&.size || 0).positive?
1060
+ size = var_by_exp_in_cbs.size
1061
+ if size > 1
1062
+ code_for_hash = get_code_for_hash(visitor, visitor_code)
1063
+ variation_hash = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
1064
+ Logging::KameleoonLogger.debug("Calculated CBS hash %s for code '%s'", variation_hash, code_for_hash)
1065
+ idx = [(variation_hash * size).to_i, size - 1].min
1066
+ else
1067
+ idx = 0
1068
+ end
1069
+ eval_exp = EvaluatedExperiment.from_var_by_exp_rule(var_by_exp_in_cbs[idx], rule)
1070
+ end
1071
+ end
1072
+ Logging::KameleoonLogger.debug(
1073
+ "RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s) -> (eval_exp: %s)",
1074
+ visitor_code, rule, eval_exp
1075
+ )
1076
+ eval_exp
1077
+ end
1078
+
1035
1079
  ##
1036
1080
  # helper method for calculate variation key for feature flag
1037
1081
  def _calculate_variation_key_for_feature(visitor_code, feature_flag)
@@ -1042,12 +1086,12 @@ module Kameleoon
1042
1086
  visitor = @visitor_manager.get_visitor(visitor_code)
1043
1087
  code_for_hash = get_code_for_hash(visitor, visitor_code)
1044
1088
  # no rules -> return default_variation_key
1045
- selected = nil
1089
+ eval_exp = nil
1046
1090
  feature_flag.rules.each do |rule|
1047
1091
  forced_variation = visitor&.get_forced_experiment_variation(rule.experiment.id)
1048
1092
  if forced_variation&.force_targeting
1049
1093
  # Forcing experiment variation in force-targeting mode
1050
- selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
1094
+ eval_exp = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
1051
1095
  break
1052
1096
  end
1053
1097
  # check if visitor is targeted for rule, else next rule
@@ -1055,7 +1099,7 @@ module Kameleoon
1055
1099
 
1056
1100
  unless forced_variation.nil?
1057
1101
  # Forcing experiment variation in targeting-only mode
1058
- selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
1102
+ eval_exp = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
1059
1103
  break
1060
1104
  end
1061
1105
  # uses for rule exposition
@@ -1063,8 +1107,11 @@ module Kameleoon
1063
1107
  Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
1064
1108
  # check main expostion for rule with hashRule
1065
1109
  if hash_rule <= rule.exposition
1110
+ eval_exp = evaluate_cbscores(visitor, visitor_code, rule)
1111
+ break unless eval_exp.nil?
1112
+
1066
1113
  if rule.targeted_delivery_type?
1067
- selected = EvaluatedExperiment.from_var_by_exp_rule(rule.experiment.variations_by_exposition.first, rule)
1114
+ eval_exp = EvaluatedExperiment.from_var_by_exp_rule(rule.experiment.variations_by_exposition.first, rule)
1068
1115
  break
1069
1116
  end
1070
1117
  # uses for variation's expositions
@@ -1073,7 +1120,7 @@ module Kameleoon
1073
1120
  # get variation key with new hashVariation
1074
1121
  variation = rule.experiment.get_variation(hash_variation)
1075
1122
  unless variation.nil?
1076
- selected = EvaluatedExperiment.from_var_by_exp_rule(variation, rule)
1123
+ eval_exp = EvaluatedExperiment.from_var_by_exp_rule(variation, rule)
1077
1124
  break
1078
1125
  end
1079
1126
  # if visitor is targeted for targeted rule then break cycle -> return default
@@ -1083,9 +1130,9 @@ module Kameleoon
1083
1130
  end
1084
1131
  Logging::KameleoonLogger.debug(
1085
1132
  "RETURN: KameleoonClient._calculate_variation_key_for_feature(visitor_code: '%s', feature_flag: %s) " \
1086
- '-> (eval_exp: %s)', visitor_code, feature_flag, selected
1133
+ '-> (eval_exp: %s)', visitor_code, feature_flag, eval_exp
1087
1134
  )
1088
- selected
1135
+ eval_exp
1089
1136
  end
1090
1137
 
1091
1138
  def calculate_variation_key(eval_exp, default_value)
@@ -14,7 +14,7 @@ module Kameleoon
14
14
 
15
15
  attr_reader :client_id, :client_secret, :refresh_interval_second, :session_duration_second,
16
16
  :default_timeout_millisecond, :tracking_interval_second, :environment, :top_level_domain,
17
- :verbose_mode
17
+ :verbose_mode, :network_domain
18
18
 
19
19
  def to_s
20
20
  'KameleoonClientConfig{' \
@@ -25,7 +25,8 @@ module Kameleoon
25
25
  "environment:'#{@environment}'," \
26
26
  "default_timeout_millisecond:#{@default_timeout_millisecond}," \
27
27
  "top_level_domain:'#{@top_level_domain}'," \
28
- "verbose_mode:#{verbose_mode}" \
28
+ "verbose_mode:#{verbose_mode}," \
29
+ "network_domain:'#{@network_domain}'" \
29
30
  '}'
30
31
  end
31
32
 
@@ -39,7 +40,8 @@ module Kameleoon
39
40
  tracking_interval_millisecond: DEFAULT_TRACKING_INTERVAL_MILLISECONDS,
40
41
  environment: nil,
41
42
  top_level_domain: nil,
42
- verbose_mode: nil
43
+ verbose_mode: nil,
44
+ network_domain: nil
43
45
  )
44
46
  raise Exception::ConfigCredentialsInvalid, 'Client ID is not specified' if client_id&.empty? != false
45
47
  raise Exception::ConfigCredentialsInvalid, 'Client secret is not specified' if client_secret&.empty? != false
@@ -114,6 +116,7 @@ module Kameleoon
114
116
  )
115
117
  end
116
118
  @top_level_domain = Utils::Domain.validate_top_level_domain(top_level_domain)
119
+ @network_domain = Utils::Domain.validate_network_domain(network_domain)
117
120
  end
118
121
 
119
122
  def self.read_from_yaml(path)
@@ -131,7 +134,8 @@ module Kameleoon
131
134
  tracking_interval_millisecond: yaml['tracking_interval_millisecond'],
132
135
  environment: yaml['environment'],
133
136
  top_level_domain: yaml['top_level_domain'],
134
- verbose_mode: yaml['verbose_mode']
137
+ verbose_mode: yaml['verbose_mode'],
138
+ network_domain: yaml['network_domain']
135
139
  )
136
140
  end
137
141
  end
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'kameleoon/data/manager/page_view_visit'
4
- require 'kameleoon/data/manager/assigned_variation'
5
- require 'kameleoon/data/visitor_visits'
6
3
  require 'kameleoon/data/browser'
7
- require 'kameleoon/data/device'
4
+ require 'kameleoon/data/cbscores'
8
5
  require 'kameleoon/data/conversion'
9
6
  require 'kameleoon/data/custom_data'
7
+ require 'kameleoon/data/device'
10
8
  require 'kameleoon/data/geolocation'
11
9
  require 'kameleoon/data/kcs_heat'
12
10
  require 'kameleoon/data/page_view'
11
+ require 'kameleoon/data/visitor_visits'
12
+ require 'kameleoon/data/manager/page_view_visit'
13
+ require 'kameleoon/data/manager/assigned_variation'
13
14
 
14
15
  module Kameleoon
15
16
  module Managers
16
17
  module RemoteData
17
18
  class RemoteVisitorData
18
19
  attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :device, :browser,
19
- :operating_system, :geolocation, :previous_visitor_visits, :kcs_heat, :visitor_code
20
+ :operating_system, :geolocation, :previous_visitor_visits, :kcs_heat, :cbs, :visitor_code
20
21
 
21
22
  def initialize(hash)
22
23
  current_visit = hash['currentVisit']
@@ -33,6 +34,7 @@ module Kameleoon
33
34
  @previous_visitor_visits = VisitorVisits.new(times_started)
34
35
  end
35
36
  @kcs_heat = parse_kcs_heat(hash['kcs'])
37
+ @cbs = parse_cbscores(hash['cbs'])
36
38
  end
37
39
 
38
40
  def collect_data_to_add
@@ -40,6 +42,7 @@ module Kameleoon
40
42
  data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
41
43
  data_to_add.push(@previous_visitor_visits) unless @previous_visitor_visits.nil?
42
44
  data_to_add.push(@kcs_heat) unless @kcs_heat.nil?
45
+ data_to_add.push(@cbs) unless @cbs.nil?
43
46
  data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
44
47
  data_to_add.concat(@experiments.values) unless @experiments.nil?
45
48
  data_to_add.concat(conversions_single_objects)
@@ -184,6 +187,26 @@ module Kameleoon
184
187
  end
185
188
  KcsHeat.new(value_map)
186
189
  end
190
+
191
+ def parse_cbscores(cbs)
192
+ return nil if cbs.nil?
193
+
194
+ cbs_map = {}
195
+ cbs.each do |str_exp_id, scored_var_entries|
196
+ next unless str_exp_id.is_a?(String) && scored_var_entries.is_a?(Hash)
197
+
198
+ entries = []
199
+ scored_var_entries.each do |str_var_id, score|
200
+ next unless str_var_id.is_a?(String) && (score.is_a?(Float) || score.is_a?(Integer))
201
+
202
+ var_id = str_var_id.to_i
203
+ entries.push(CBScores::ScoredVarId.new(var_id, score))
204
+ end
205
+ exp_id = str_exp_id.to_i
206
+ cbs_map[exp_id] = entries
207
+ end
208
+ CBScores.new(cbs_map)
209
+ end
187
210
  end
188
211
  end
189
212
  end
@@ -88,13 +88,13 @@ module Kameleoon
88
88
  if response.success?
89
89
  success = true
90
90
  elsif !response.error.nil?
91
- log_level = attempt < retry_limit ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
91
+ log_level = get_log_level(attempt, retry_limit)
92
92
  Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Error occurred during request: %s",
93
93
  request.method, request.url, response.error)
94
94
  else
95
- log_level = attempt < retry_limit ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
96
- Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Received unexpected status code '%s'",
97
- request.method, request.url, response.code)
95
+ log_level = get_log_level(attempt, retry_limit)
96
+ Logging::KameleoonLogger.log(log_level, "%s call '%s' failed: Received unexpected status code: %s, body: %s",
97
+ request.method, request.url, response.code, response.body)
98
98
  if response.code == 401 && request.access_token
99
99
  Logging::KameleoonLogger.warning("Unexpected rejection of access token '#{request.access_token}'")
100
100
  @access_token_source.discard_token(request.access_token)
@@ -113,6 +113,10 @@ module Kameleoon
113
113
  success ? response.body : false
114
114
  end
115
115
 
116
+ def get_log_level(attempt, attempt_count)
117
+ return log_level = attempt < attempt_count ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
118
+ end
119
+
116
120
  def delay(period)
117
121
  sleep(period)
118
122
  end
@@ -14,27 +14,43 @@ module Kameleoon
14
14
  POST_DATA_PATH = '/map/maps'
15
15
  ACCESS_TOKEN_PATH = '/oauth/token'
16
16
 
17
- CONFIGURATION_API_URL_FORMAT = 'https://sdk-config.kameleoon.eu/%s'
18
- RT_CONFIGURATION_URL = 'https://events.kameleoon.com:8110/sse'
17
+ DEFAULT_EVENTS_DOMAIN = 'events.kameleoon.eu'
18
+ DEFAULT_CONFIGURATION_DOMAIN = 'sdk-config.kameleoon.eu'
19
+ DEFAULT_ACCESS_TOKEN_DOMAIN = 'api.kameleoon.com'
19
20
  DEFAULT_DATA_API_DOMAIN = 'data.kameleoon.io'
21
+
22
+ CONFIGURATION_API_URL_FORMAT = 'https://%s/%s'
23
+ DATA_API_URL_FORMAT = 'https://%s%s?%s'
24
+ RT_CONFIGURATION_URL_FORMAT = 'https://%s:8110/sse?%s'
25
+ ACCESS_TOKEN_URL_FORMAT = 'https://%s/oauth/token'
26
+
20
27
  TEST_DATA_API_DOMAIN = 'data.kameleoon.net'
21
- DEFAULT_AUTOMATION_API_DOMAIN = 'api.kameleoon.com'
22
28
  TEST_AUTOMATION_API_DOMAIN = 'api.kameleoon.net'
23
29
 
24
- attr_reader :site_code, :data_api_domain, :automation_api_domain
30
+ attr_reader :site_code, :data_api_domain, :events_domain, :configuration_domain, :access_token_domain
25
31
 
26
32
  def initialize(
27
33
  site_code,
28
- data_api_domain = DEFAULT_DATA_API_DOMAIN,
29
- automation_api_domain = DEFAULT_AUTOMATION_API_DOMAIN
34
+ network_domain = nil
30
35
  )
31
36
  @site_code = site_code
32
- @data_api_domain = data_api_domain
33
- @automation_api_domain = automation_api_domain
37
+ @data_api_domain = DEFAULT_DATA_API_DOMAIN
38
+ @events_domain = DEFAULT_EVENTS_DOMAIN
39
+ @configuration_domain = DEFAULT_CONFIGURATION_DOMAIN
40
+ @access_token_domain = DEFAULT_ACCESS_TOKEN_DOMAIN
41
+ @is_custom_domain = false
42
+ update_domains(network_domain)
34
43
  end
35
44
 
36
45
  def apply_data_api_domain(domain)
37
- @data_api_domain = domain if domain.is_a?(String)
46
+ return if domain.nil? || domain.empty?
47
+
48
+ if @is_custom_domain
49
+ sub_domain = domain.split('.').first
50
+ @data_api_domain = @data_api_domain.sub(/^[^.]+/, sub_domain)
51
+ else
52
+ @data_api_domain = domain
53
+ end
38
54
  end
39
55
 
40
56
  def make_tracking_url
@@ -44,7 +60,7 @@ module Kameleoon
44
60
  siteCode: @site_code,
45
61
  bodyUa: true
46
62
  }
47
- "https://#{@data_api_domain}#{TRACKING_PATH}?#{UriHelper.encode_query(params)}"
63
+ format(DATA_API_URL_FORMAT, @data_api_domain, TRACKING_PATH, UriHelper.encode_query(params))
48
64
  end
49
65
 
50
66
  def make_visitor_data_get_url(visitor_code, filter, is_unique_identifier = false)
@@ -60,7 +76,8 @@ module Kameleoon
60
76
  params[:experiment] = true if filter.experiments
61
77
  params[:page] = true if filter.page_views
62
78
  params[:staticData] = true if filter.device || filter.browser || filter.operating_system
63
- "https://#{@data_api_domain}#{VISITOR_DATA_PATH}?#{UriHelper.encode_query(params)}"
79
+ params[:cbs] = true if filter.cbs
80
+ format(DATA_API_URL_FORMAT, @data_api_domain, VISITOR_DATA_PATH, UriHelper.encode_query(params))
64
81
  end
65
82
 
66
83
  def make_api_data_get_request_url(key)
@@ -68,11 +85,11 @@ module Kameleoon
68
85
  siteCode: @site_code,
69
86
  key: key
70
87
  }
71
- "https://#{@data_api_domain}#{GET_DATA_PATH}?#{UriHelper.encode_query(params)}"
88
+ format(DATA_API_URL_FORMAT, @data_api_domain, GET_DATA_PATH, UriHelper.encode_query(params))
72
89
  end
73
90
 
74
91
  def make_configuration_url(environment = nil, timestamp = nil)
75
- url = format(CONFIGURATION_API_URL_FORMAT, @site_code)
92
+ url = format(CONFIGURATION_API_URL_FORMAT, @configuration_domain, @site_code)
76
93
  params = {}
77
94
  params[:environment] = environment unless environment.nil?
78
95
  params[:ts] = timestamp unless timestamp.nil?
@@ -82,11 +99,24 @@ module Kameleoon
82
99
 
83
100
  def make_real_time_url
84
101
  params = { siteCode: @site_code }
85
- "#{RT_CONFIGURATION_URL}?#{UriHelper.encode_query(params)}"
102
+ format(RT_CONFIGURATION_URL_FORMAT, @events_domain, UriHelper.encode_query(params))
86
103
  end
87
104
 
88
105
  def make_access_token_url
89
- "https://#{@automation_api_domain}#{ACCESS_TOKEN_PATH}"
106
+ format(ACCESS_TOKEN_URL_FORMAT, @access_token_domain)
107
+ end
108
+
109
+ private
110
+
111
+ def update_domains(network_domain)
112
+ return if network_domain.nil? || network_domain.empty?
113
+
114
+ @is_custom_domain = true
115
+
116
+ @data_api_domain = "data.#{network_domain}"
117
+ @events_domain = "events.#{network_domain}"
118
+ @configuration_domain = "sdk-config.#{network_domain}"
119
+ @access_token_domain = "api.#{network_domain}"
90
120
  end
91
121
  end
92
122
  end
@@ -5,7 +5,7 @@ module Kameleoon
5
5
  module Types
6
6
  class RemoteVisitorDataFilter
7
7
  attr_reader :previous_visit_amount, :current_visit, :custom_data, :page_views, :geolocation, :device, :browser,
8
- :operating_system, :conversions, :experiments, :kcs, :visitor_code
8
+ :operating_system, :conversions, :experiments, :kcs, :visitor_code, :cbs
9
9
 
10
10
  def to_s
11
11
  "RemoteVisitorDataFilter{previous_visit_amount:#{@previous_visit_amount}," \
@@ -19,13 +19,14 @@ module Kameleoon
19
19
  "conversions:#{@conversions}," \
20
20
  "experiments:#{@experiments}," \
21
21
  "kcs:#{@kcs}," \
22
- "visitor_code:#{@visitor_code}}"
22
+ "visitor_code:#{@visitor_code}}" \
23
+ "cbs:#{@cbs}}"
23
24
  end
24
25
 
25
26
  def initialize(
26
27
  previous_visit_amount: 1, current_visit: true, custom_data: true, page_views: false,
27
28
  geolocation: false, device: false, browser: false, operating_system: false, conversions: false,
28
- experiments: false, kcs: false, visitor_code: true
29
+ experiments: false, kcs: false, visitor_code: true, cbs: false
29
30
  )
30
31
  @previous_visit_amount = previous_visit_amount
31
32
  @current_visit = current_visit
@@ -39,6 +40,7 @@ module Kameleoon
39
40
  @experiments = experiments
40
41
  @kcs = kcs
41
42
  @visitor_code = visitor_code
43
+ @cbs = cbs
42
44
  end
43
45
  end
44
46
  end
@@ -50,6 +50,22 @@ module Kameleoon
50
50
  end
51
51
 
52
52
  module Strval
53
+ def self.obj_to_s(obj)
54
+ case obj
55
+ when nil
56
+ 'nil'
57
+ when String
58
+ "'#{obj}'"
59
+ when Array
60
+ "[#{obj.map { |v| obj_to_s(v) }.join(',')}]"
61
+ when Hash
62
+ sb = []
63
+ obj.each { |k, v| sb.push("#{obj_to_s(k)}:#{obj_to_s(v)}") }
64
+ "{#{sb.join(',')}}"
65
+ else
66
+ obj.to_s
67
+ end
68
+ end
53
69
 
54
70
  def self.secret(secret)
55
71
  hid_ch = '*'
@@ -77,17 +93,7 @@ module Kameleoon
77
93
  def self.validate_top_level_domain(top_level_domain)
78
94
  return nil if top_level_domain.nil? || top_level_domain.empty?
79
95
 
80
- top_level_domain = top_level_domain.downcase
81
-
82
- [HTTP, HTTPS].each do |protocol|
83
- next unless top_level_domain.start_with?(protocol)
84
-
85
- top_level_domain = top_level_domain[protocol.length..]
86
- Logging::KameleoonLogger.warning(
87
- "The top-level domain contains '%s'. Domain after protocol trimming: '%s'", protocol, top_level_domain
88
- )
89
- break
90
- end
96
+ top_level_domain = check_and_trim_protocol(top_level_domain.downcase)
91
97
 
92
98
  if !REGEX_DOMAIN.match?(top_level_domain) && top_level_domain != LOCALHOST
93
99
  Logging::KameleoonLogger.error(
@@ -100,6 +106,41 @@ module Kameleoon
100
106
 
101
107
  top_level_domain
102
108
  end
109
+
110
+ def self.validate_network_domain(network_domain)
111
+ return nil if network_domain.nil? || network_domain.empty?
112
+
113
+ network_domain = check_and_trim_protocol(network_domain.downcase)
114
+
115
+ # remove first and last dot
116
+ network_domain = network_domain.gsub(/^\.+|\.+$/, '')
117
+
118
+ if !REGEX_DOMAIN.match?(network_domain) && network_domain != LOCALHOST
119
+ Logging::KameleoonLogger.error(
120
+ "The network domain '%s' is invalid.",
121
+ network_domain
122
+ )
123
+ return nil
124
+ end
125
+
126
+ network_domain
127
+ end
128
+
129
+ private
130
+
131
+ def self.check_and_trim_protocol(domain)
132
+ [HTTP, HTTPS].each do |protocol|
133
+ next unless domain.start_with?(protocol)
134
+
135
+ domain = domain[protocol.length..]
136
+ Logging::KameleoonLogger.warning(
137
+ "The domain contains '%s'. Domain after protocol trimming: '%s'", protocol, domain
138
+ )
139
+ break
140
+ end
141
+
142
+ domain
143
+ end
103
144
  end
104
145
  end
105
146
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kameleoon
4
- SDK_VERSION = '3.9.0'
4
+ SDK_VERSION = '3.11.0'
5
5
  SDK_NAME = 'RUBY'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kameleoon-client-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.0
4
+ version: 3.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kameleoon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-26 00:00:00.000000000 Z
11
+ date: 2025-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -87,6 +87,7 @@ files:
87
87
  - lib/kameleoon/configuration/variation.rb
88
88
  - lib/kameleoon/configuration/variation_exposition.rb
89
89
  - lib/kameleoon/data/browser.rb
90
+ - lib/kameleoon/data/cbscores.rb
90
91
  - lib/kameleoon/data/conversion.rb
91
92
  - lib/kameleoon/data/cookie.rb
92
93
  - lib/kameleoon/data/custom_data.rb