kameleoon-client-ruby 3.13.0 → 3.15.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: 05500f77891f22fd67b10f0dec830e6673a77d8ff83b391ffbf8547893bfcf1e
4
- data.tar.gz: 4c572f1464af6e5026642e4fac03dcc1e1a99715228c76982c67de5063c2235e
3
+ metadata.gz: 07240065fc16f98169c01fd4367321b2f633d79373f293b0bcf110f68d920d46
4
+ data.tar.gz: 52b83806f28aa0035eb8df2db89f84b98b774e2a53b0fd8a7c5abd71b0a59ac4
5
5
  SHA512:
6
- metadata.gz: b7ac1acb43bf56661f194dacc810b064e2c4cba67010b1af49d928c190ae53c139ee0447fb7e5f5670ae1fb0c406a0efb7e2c7fe0eedac323635dc0e215de869
7
- data.tar.gz: 58278744be7aaef43a0e0dcbe71bf93ee9ab9c1aec3a0b6e6f296e940f24ce943ee7c7998a550ecf1950cbcb3f36cb78858feacea6289a2b8ce1c3439b4b037e
6
+ metadata.gz: 88e8da4ec50eca784dd49b5861f373d84e60eaa521894d095104c10d6d469cb8e20d4fbf98416a473790fe77842411e4021beed153a4a4bb784603b90360abd0
7
+ data.tar.gz: 9068a2d4b8dfaff1020933c9e38e133965486ef0ddc722db19dd7eeee28a0327926d4d3beb1b17460615da176620699a2f7dcc5ada1dc63db4e6696aaa080131
@@ -13,16 +13,18 @@ module Kameleoon
13
13
  def initialize(hashes)
14
14
  @local_only = Set[]
15
15
  @visitor_scope = Set[]
16
+ @custom_data_index_by_id = {}
16
17
  unless hashes.nil?
17
18
  for hash in hashes
18
19
  index = hash['index']
19
20
  @local_only.add(index) if hash['localOnly']
20
21
  @visitor_scope.add(index) if hash['scope'] == SCOPE_VISITOR
22
+ id = hash['id']
23
+ @custom_data_index_by_id[id] = index if id && index
21
24
  if hash['isMappingIdentifier']
22
25
  unless @mapping_identifier_index.nil?
23
26
  Logging::KameleoonLogger.warning('More than one mapping identifier is set. Undefined behavior ' \
24
27
  'may occur on cross-device reconciliation.')
25
-
26
28
  end
27
29
  @mapping_identifier_index = index
28
30
  end
@@ -42,6 +44,10 @@ module Kameleoon
42
44
  @visitor_scope.include?(index)
43
45
  end
44
46
 
47
+ def get_custom_data_index_by_id(custom_data_id)
48
+ @custom_data_index_by_id[custom_data_id]
49
+ end
50
+
45
51
  def self.mapping_identifier?(custom_data_info, custom_data)
46
52
  !custom_data_info.nil? && (custom_data.id == custom_data_info.mapping_identifier_index) && \
47
53
  !(custom_data.values.empty? || custom_data.values[0].empty?)
@@ -6,13 +6,14 @@ require 'kameleoon/configuration/feature_flag'
6
6
  require 'kameleoon/configuration/me_group'
7
7
  require 'kameleoon/configuration/settings'
8
8
  require 'kameleoon/logging/kameleoon_logger'
9
+ require 'kameleoon/targeting/models'
9
10
 
10
11
  module Kameleoon
11
12
  module Configuration
12
13
  class DataFile
13
- attr_reader :last_modified, :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule,
14
- :feature_flag_by_id, :rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
15
- :experiment_ids_with_js_css_variable, :holdout
14
+ attr_reader :last_modified, :settings, :segments, :audience_tracking_segments, :feature_flags, :me_groups,
15
+ :has_any_targeted_delivery_rule, :feature_flag_by_id, :rule_info_by_exp_id, :variation_by_id,
16
+ :custom_data_info, :experiment_ids_with_js_css_variable, :holdout
16
17
 
17
18
  def to_s
18
19
  'DataFile{' \
@@ -69,25 +70,40 @@ module Kameleoon
69
70
  def init(configuration)
70
71
  Logging::KameleoonLogger.debug('CALL: DataFile.init(configuration: %s)', configuration)
71
72
  @settings = Settings.new(configuration['configuration'])
72
- @feature_flags = {}
73
- configuration['featureFlags'].each do |raw|
74
- ff = FeatureFlag.new(raw)
75
- @feature_flags[ff.feature_key] = ff
76
- end
73
+ @segments, @audience_tracking_segments = parse_segments(configuration)
74
+ @custom_data_info = CustomDataInfo.new(configuration['customData'])
75
+ @feature_flags = parse_feature_flags(configuration, @custom_data_info)
77
76
  @me_groups = make_me_groups(@feature_flags)
78
77
  @has_any_targeted_delivery_rule = any_targeted_delivery_rule?
79
- @custom_data_info = CustomDataInfo.new(configuration['customData'])
80
78
  @holdout = Experiment.from_json(configuration['holdout']) if configuration.include?('holdout')
81
79
  Logging::KameleoonLogger.debug('RETURN: DataFile.init(configuration: %s)', configuration)
82
80
  end
83
81
 
82
+ def parse_segments(configuration)
83
+ audience_tracking_segments = []
84
+ segments = configuration['segments'].to_h do |raw_seg|
85
+ seg = Targeting::Segment.new(raw_seg)
86
+ audience_tracking_segments.push(seg) if seg.audience_tracking
87
+ [seg.id, seg]
88
+ end
89
+ segments.freeze
90
+ audience_tracking_segments.freeze
91
+ [segments, audience_tracking_segments]
92
+ end
93
+
94
+ def parse_feature_flags(configuration, cdi)
95
+ configuration['featureFlags'].to_h do |raw_ff|
96
+ ff = FeatureFlag.new(raw_ff, @segments, cdi)
97
+ [ff.feature_key, ff]
98
+ end
99
+ end
100
+
84
101
  def any_targeted_delivery_rule?
85
102
  @feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
86
103
  end
87
104
 
88
105
  def collect_indices
89
106
  @feature_flag_by_id = {}
90
- @rule_by_segment_id = {}
91
107
  @rule_info_by_exp_id = {}
92
108
  @variation_by_id = {}
93
109
  @experiment_ids_with_js_css_variable = Set.new
@@ -98,7 +114,6 @@ module Kameleoon
98
114
 
99
115
  has_feature_flag_variable_js_css = feature_flag_variable_js_css?(feature_flag)
100
116
  feature_flag.rules.each do |rule|
101
- @rule_by_segment_id[rule.segment_id] = rule
102
117
  @rule_info_by_exp_id[rule.experiment.id] = RuleInfo.new(feature_flag, rule)
103
118
  rule.experiment.variations_by_exposition.each do |variation|
104
119
  @variation_by_id[variation.variation_id] = variation
@@ -107,7 +122,6 @@ module Kameleoon
107
122
  end
108
123
  end
109
124
  @feature_flag_by_id.freeze
110
- @rule_by_segment_id.freeze
111
125
  @variation_by_id.freeze
112
126
  @experiment_ids_with_js_css_variable.freeze
113
127
  end
@@ -8,7 +8,8 @@ module Kameleoon
8
8
  module Configuration
9
9
  # Class for manage all feature flags with rules
10
10
  class FeatureFlag
11
- attr_accessor :id, :feature_key, :variations, :default_variation_key, :me_group_name, :environment_enabled, :rules
11
+ attr_reader :id, :feature_key, :variations, :default_variation_key, :me_group_name, :environment_enabled, :rules,
12
+ :bucketing_custom_data_index
12
13
 
13
14
  def self.create_from_array(array)
14
15
  array&.map { |it| FeatureFlag.new(it) }
@@ -21,18 +22,21 @@ module Kameleoon
21
22
  "environment_enabled:#{@environment_enabled}," \
22
23
  "default_variation_key:'#{@default_variation_key}'," \
23
24
  "me_group_name:'#{@me_group_name}'," \
24
- "rules:#{@rules.size}" \
25
+ "rules:#{@rules.size}," \
26
+ "bucketing_custom_data_index:#{@bucketing_custom_data_index}" \
25
27
  '}'
26
28
  end
27
29
 
28
- def initialize(hash)
30
+ def initialize(hash, segments, cdi)
29
31
  @id = hash['id']
30
32
  @feature_key = hash['featureKey']
31
33
  @variations = Variation.create_from_array(hash['variations'])
32
34
  @default_variation_key = hash['defaultVariationKey']
33
35
  @me_group_name = hash['mutuallyExclusiveGroup']
34
36
  @environment_enabled = hash['environmentEnabled']
35
- @rules = Rule.create_from_array(hash['rules'])
37
+ @rules = Rule.create_from_array(hash['rules'], segments)
38
+ bucketing_cd_id = hash['bucketingCustomDataId']
39
+ @bucketing_custom_data_index = bucketing_cd_id ? cdi.get_custom_data_index_by_id(bucketing_cd_id) : nil
36
40
  end
37
41
 
38
42
  def get_variation_by_key(key)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'experiment'
4
- require 'kameleoon/targeting/models'
5
4
 
6
5
  module Kameleoon
7
6
  # Module which contains all internal data of SDK
@@ -32,22 +31,22 @@ module Kameleoon
32
31
  attr_reader :id, :order, :type, :exposition, :experiment, :respool_time, :segment_id
33
32
  attr_accessor :targeting_segment
34
33
 
35
- def self.create_from_array(array)
36
- array&.map { |it| Rule.new(it) }
34
+ def self.create_from_array(array, segments)
35
+ array&.map { |it| Rule.new(it, segments) }
37
36
  end
38
37
 
39
38
  def to_s
40
39
  "Rule{id:#{@id}}"
41
40
  end
42
41
 
43
- def initialize(hash)
42
+ def initialize(hash, segments)
44
43
  @id = hash['id']
45
44
  @order = hash['order']
46
45
  @type = RuleType.from_literal(hash['type'])
47
46
  @exposition = hash['exposition']
48
47
  @respool_time = hash['respoolTime']
49
- @targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
50
- @segment_id = @targeting_segment != nil ? targeting_segment.id : -1
48
+ @segment_id = hash['segmentId'] || -1
49
+ @targeting_segment = segments[@segment_id] if @segment_id != -1
51
50
  @experiment = Experiment.from_json(hash)
52
51
  end
53
52
 
@@ -15,4 +15,3 @@ module Kameleoon
15
15
  end
16
16
  end
17
17
  end
18
-
@@ -19,6 +19,7 @@ module Kameleoon
19
19
  OPERATING_SYSTEM = 'OPERATING_SYSTEM'
20
20
  GEOLOCATION = 'GEOLOCATION'
21
21
  VISITOR_VISITS = 'VISITOR_VISITS'
22
+ TARGETED_SEGMENT = 'TARGETED_SEGMENT'
22
23
  end
23
24
 
24
25
  module DataState
@@ -10,6 +10,7 @@ require 'kameleoon/data/device'
10
10
  require 'kameleoon/data/kcs_heat'
11
11
  require 'kameleoon/data/page_view'
12
12
  require 'kameleoon/data/personalization'
13
+ require 'kameleoon/data/targeted_segment'
13
14
  require 'kameleoon/data/unique_identifier'
14
15
  require 'kameleoon/data/user_agent'
15
16
  require 'kameleoon/data/manager/assigned_variation'
@@ -186,6 +187,14 @@ module Kameleoon
186
187
  personalizations
187
188
  end
188
189
 
190
+ def targeted_segments
191
+ targeted_segments = @data.targeted_segments
192
+ Logging::KameleoonLogger.debug(
193
+ 'CALL/RETURN: Visitor.targeted_segments -> (targeted_segments: %s)', targeted_segments
194
+ )
195
+ targeted_segments
196
+ end
197
+
189
198
  def get_forced_feature_variation(feature_key)
190
199
  Logging::KameleoonLogger.debug("CALL: Visitor.get_forced_feature_variation(feature_key: '%s')", feature_key)
191
200
  variation = @data.get_from_map(@data.simulated_variations, feature_key)
@@ -241,6 +250,8 @@ module Kameleoon
241
250
  @data.add_variation(data, overwrite)
242
251
  when Personalization
243
252
  @data.add_personalization(data, overwrite)
253
+ when TargetedSegment
254
+ @data.add_targeted_segment(data)
244
255
  when ForcedFeatureVariation
245
256
  @data.add_forced_feature_variation(data)
246
257
  when ForcedExperimentVariation
@@ -329,6 +340,7 @@ module Kameleoon
329
340
  @custom_data_map&.each { |_, cd| blk.call(cd) }
330
341
  @page_view_visits&.each { |_, pvv| blk.call(pvv.page_view) }
331
342
  @variations&.each { |_, av| blk.call(av) }
343
+ @targeted_segments&.each { |_, ts| blk.call(ts) }
332
344
  @conversions&.each { |c| blk.call(c) }
333
345
  end
334
346
  end
@@ -344,8 +356,9 @@ module Kameleoon
344
356
  count += 1 unless @visitor_visits.nil?
345
357
  count += @custom_data_map.size unless @custom_data_map.nil?
346
358
  count += @page_view_visits.size unless @page_view_visits.nil?
347
- count += @conversions.size unless @conversions.nil?
348
359
  count += @variations.size unless @variations.nil?
360
+ count += @targeted_segments.size unless @targeted_segments.nil?
361
+ count += @conversions.size unless @conversions.nil?
349
362
  end
350
363
  Logging::KameleoonLogger.debug('RETURN: VisitorData.count_sendable_data -> (count: %s)', count)
351
364
  count
@@ -371,6 +384,10 @@ module Kameleoon
371
384
  DataMapStorage.new(@mutex, @personalizations)
372
385
  end
373
386
 
387
+ def targeted_segments
388
+ DataMapStorage.new(@mutex, @targeted_segments)
389
+ end
390
+
374
391
  def set_device(device, overwrite)
375
392
  @device = device if overwrite || @device.nil?
376
393
  end
@@ -389,6 +406,11 @@ module Kameleoon
389
406
  end
390
407
  end
391
408
 
409
+ def add_targeted_segment(targeted_segment)
410
+ @targeted_segments ||= {}
411
+ @targeted_segments[targeted_segment.id] = targeted_segment
412
+ end
413
+
392
414
  def set_browser(browser, overwrite)
393
415
  @browser = browser if overwrite || @browser.nil?
394
416
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kameleoon/data/data'
4
+ require 'kameleoon/network/uri_helper'
5
+
6
+ module Kameleoon
7
+ class TargetedSegment < DuplicationUnsafeData
8
+ EVENT_TYPE = 'targetingSegment'
9
+
10
+ attr_reader :id
11
+
12
+ def initialize(id)
13
+ super(DataType::TARGETED_SEGMENT)
14
+ @id = id
15
+ end
16
+
17
+ def obtain_full_post_text_line
18
+ params = {
19
+ eventType: EVENT_TYPE,
20
+ id: @id,
21
+ nonce: nonce
22
+ }
23
+ Network::UriHelper.encode_query(params)
24
+ end
25
+
26
+ def to_s
27
+ "TargetedSegment{id:#{@id}}"
28
+ end
29
+ end
30
+ end
@@ -758,6 +758,27 @@ module Kameleoon
758
758
  )
759
759
  end
760
760
 
761
+ ##
762
+ # Evaluates the visitor against all available Audiences Explorer segments and tracks those that match.
763
+ # A detailed analysis of segment performance can then be performed directly in Audiences Explorer.
764
+ #
765
+ # @param [String] visitor_code The unique visitor code identifying the visitor.
766
+ #
767
+ # @raise [Kameleoon::Exception::VisitorCodeInvalid] The provided **visitor code** is invalid.
768
+ def evaluate_audiences(visitor_code)
769
+ Logging::KameleoonLogger.info("CALL: KameleoonClient.evaluate_audiences(visitor_code: '%s')", visitor_code)
770
+ Utils::VisitorCode.validate(visitor_code)
771
+ segments = @data_manager.data_file.audience_tracking_segments.select do |seg|
772
+ check_targeting(visitor_code, nil, seg)
773
+ end
774
+ unless segments.empty?
775
+ segments.map! { |seg| TargetedSegment.new(seg.id) }
776
+ @visitor_manager.add_data(visitor_code, *segments)
777
+ end
778
+ @tracking_manager.add_visitor_code(visitor_code)
779
+ Logging::KameleoonLogger.info("RETURN: KameleoonClient.evaluate_audiences(visitor_code: '%s')", visitor_code)
780
+ end
781
+
761
782
  private
762
783
 
763
784
  HYBRID_EXPIRATION_TIME = 5
@@ -904,8 +925,8 @@ module Kameleoon
904
925
  # nil
905
926
  # end
906
927
 
907
- def check_targeting(visitor_code, campaign_id, exp_ff_rule)
908
- @targeting_manager.check_targeting(visitor_code, campaign_id, exp_ff_rule)
928
+ def check_targeting(visitor_code, campaign_id, segment)
929
+ @targeting_manager.check_targeting(visitor_code, campaign_id, segment)
909
930
  end
910
931
 
911
932
  def get_variation_info(visitor_code, feature_flag, track)
@@ -933,7 +954,7 @@ module Kameleoon
933
954
  forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
934
955
  if forced_variation
935
956
  eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
936
- elsif visitor_not_in_holdout?(visitor, visitor_code, track, save) && \
957
+ elsif visitor_not_in_holdout?(visitor, visitor_code, track, save, feature_flag.bucketing_custom_data_index) && \
937
958
  ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
938
959
  eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
939
960
  end
@@ -955,7 +976,7 @@ module Kameleoon
955
976
  unrestricted = true
956
977
  me_group = @data_manager.data_file.me_groups[feature_flag.me_group_name]
957
978
  if me_group
958
- code_for_hash = get_code_for_hash(visitor, visitor_code)
979
+ code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
959
980
  me_group_hash = Utils::Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
960
981
  Logging::KameleoonLogger.debug(
961
982
  "Calculated ME group hash %s for code: '%s', meGroup: '%s'",
@@ -970,17 +991,17 @@ module Kameleoon
970
991
  unrestricted
971
992
  end
972
993
 
973
- def visitor_not_in_holdout?(visitor, visitor_code, track, save)
994
+ def visitor_not_in_holdout?(visitor, visitor_code, track, save, bucketing_custom_data_index)
974
995
  holdout = @data_manager.data_file.holdout
975
996
  return true if holdout.nil?
976
997
 
977
998
  in_holdout_variation_key = 'in-holdout'
978
999
  Logging::KameleoonLogger.debug(
979
- "CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)",
980
- visitor_code, track, save
1000
+ "CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
1001
+ 'bucketing_custom_data_index: %s)', visitor_code, track, save, bucketing_custom_data_index
981
1002
  )
982
1003
  is_not_in_holdout = true
983
- code_for_hash = get_code_for_hash(visitor, visitor_code)
1004
+ code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
984
1005
  variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
985
1006
  Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
986
1007
  var_by_exp = holdout.get_variation(variation_hash)
@@ -992,9 +1013,9 @@ module Kameleoon
992
1013
  end
993
1014
  end
994
1015
  Logging::KameleoonLogger.debug(
995
- "RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)" \
996
- ' -> (is_not_in_holdout: %s)',
997
- visitor_code, track, save, is_not_in_holdout
1016
+ "RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
1017
+ 'bucketing_custom_data_index: %s) -> (is_not_in_holdout: %s)',
1018
+ visitor_code, track, save, bucketing_custom_data_index, is_not_in_holdout
998
1019
  )
999
1020
  is_not_in_holdout
1000
1021
  end
@@ -1038,16 +1059,24 @@ module Kameleoon
1038
1059
  )
1039
1060
  end
1040
1061
 
1041
- def get_code_for_hash(visitor, visitor_code)
1042
- # use mappingIdentifier instead of visitor_code if it was set up
1043
- visitor&.mapping_identifier || visitor_code
1062
+ def get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
1063
+ return visitor_code if visitor.nil?
1064
+
1065
+ # 1. Try to use the bucketing custom data's value if bucketingCustomDataId is defined
1066
+ if bucketing_custom_data_index
1067
+ bucketing_custom_data = visitor.custom_data.get(bucketing_custom_data_index)
1068
+ return bucketing_custom_data.values[0] if bucketing_custom_data && !bucketing_custom_data.values.empty?
1069
+ end
1070
+ # 2. Use mappingIdentifier instead of visitorCode if it was set up
1071
+ visitor.mapping_identifier || visitor_code
1044
1072
  end
1045
1073
 
1046
- def evaluate_cbscores(visitor, visitor_code, rule)
1074
+ def evaluate_cbscores(visitor, visitor_code, rule, bucketing_custom_data_index)
1047
1075
  return nil if visitor&.cbscores.nil?
1048
1076
 
1049
1077
  Logging::KameleoonLogger.debug(
1050
- "CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s)", visitor_code, rule
1078
+ "CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s, " \
1079
+ 'bucketing_custom_data_index: %s)', visitor_code, rule, bucketing_custom_data_index
1051
1080
  )
1052
1081
  eval_exp = nil
1053
1082
  var_id_group_by_scores = visitor.cbscores.values[rule.experiment.id]
@@ -1062,7 +1091,7 @@ module Kameleoon
1062
1091
  if (var_by_exp_in_cbs&.size || 0).positive?
1063
1092
  size = var_by_exp_in_cbs.size
1064
1093
  if size > 1
1065
- code_for_hash = get_code_for_hash(visitor, visitor_code)
1094
+ code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
1066
1095
  variation_hash = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
1067
1096
  Logging::KameleoonLogger.debug("Calculated CBS hash %s for code '%s'", variation_hash, code_for_hash)
1068
1097
  idx = [(variation_hash * size).to_i, size - 1].min
@@ -1073,8 +1102,8 @@ module Kameleoon
1073
1102
  end
1074
1103
  end
1075
1104
  Logging::KameleoonLogger.debug(
1076
- "RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s) -> (eval_exp: %s)",
1077
- visitor_code, rule, eval_exp
1105
+ "RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s," \
1106
+ 'bucketing_custom_data_index: %s) -> (eval_exp: %s)', visitor_code, rule, bucketing_custom_data_index, eval_exp
1078
1107
  )
1079
1108
  eval_exp
1080
1109
  end
@@ -1087,7 +1116,7 @@ module Kameleoon
1087
1116
  visitor_code, feature_flag
1088
1117
  )
1089
1118
  visitor = @visitor_manager.get_visitor(visitor_code)
1090
- code_for_hash = get_code_for_hash(visitor, visitor_code)
1119
+ code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
1091
1120
  # no rules -> return default_variation_key
1092
1121
  eval_exp = nil
1093
1122
  feature_flag.rules.each do |rule|
@@ -1098,7 +1127,7 @@ module Kameleoon
1098
1127
  break
1099
1128
  end
1100
1129
  # check if visitor is targeted for rule, else next rule
1101
- next unless check_targeting(visitor_code, rule.experiment.id, rule)
1130
+ next unless check_targeting(visitor_code, rule.experiment.id, rule.targeting_segment)
1102
1131
 
1103
1132
  unless forced_variation.nil?
1104
1133
  # Forcing experiment variation in targeting-only mode
@@ -1110,7 +1139,7 @@ module Kameleoon
1110
1139
  Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
1111
1140
  # check main expostion for rule with hashRule
1112
1141
  if hash_rule <= rule.exposition
1113
- eval_exp = evaluate_cbscores(visitor, visitor_code, rule)
1142
+ eval_exp = evaluate_cbscores(visitor, visitor_code, rule, feature_flag.bucketing_custom_data_index)
1114
1143
  break unless eval_exp.nil?
1115
1144
 
1116
1145
  if rule.targeted_delivery_type?
@@ -3,6 +3,7 @@
3
3
  module Kameleoon
4
4
  module Network
5
5
  module ContentType
6
+ WILDCARD = '*/*'
6
7
  TEXT = 'text/plain'
7
8
  JSON = 'application/json'
8
9
  FORM = 'application/x-www-form-urlencoded'
@@ -66,7 +66,7 @@ module Kameleoon
66
66
 
67
67
  url = @url_provider.make_tracking_url
68
68
  timeout = ensure_timeout(timeout)
69
- request = Request.new(Method::POST, url, ContentType::TEXT, timeout, data: lines)
69
+ request = Request.new(Method::POST, url, ContentType::WILDCARD, timeout, data: lines)
70
70
  unwrap_response(*make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY))
71
71
  end
72
72
 
@@ -19,7 +19,7 @@ module Kameleoon
19
19
  DEFAULT_ACCESS_TOKEN_DOMAIN = 'api.kameleoon.com'
20
20
  DEFAULT_DATA_API_DOMAIN = 'data.kameleoon.io'
21
21
 
22
- CONFIGURATION_API_URL_FORMAT = 'https://%s/%s'
22
+ CONFIGURATION_API_URL_FORMAT = 'https://%s/v3/%s'
23
23
  DATA_API_URL_FORMAT = 'https://%s%s?%s'
24
24
  RT_CONFIGURATION_URL_FORMAT = 'https://%s:8110/sse?%s'
25
25
  ACCESS_TOKEN_URL_FORMAT = 'https://%s/oauth/token'
@@ -19,10 +19,10 @@ module Kameleoon
19
19
  private
20
20
 
21
21
  def check_targeting(segment_info)
22
- rule = segment_info.data_file.rule_by_segment_id[@segment_id]
23
- return false unless rule.is_a?(Kameleoon::Configuration::Rule)
22
+ segment = segment_info.data_file.segments[@segment_id]
23
+ return false if segment.nil?
24
24
 
25
- rule.targeting_segment.check_tree(->(type) { segment_info.condition_data(type) })
25
+ segment.check_tree(->(type) { segment_info.condition_data(type) })
26
26
  end
27
27
  end
28
28
 
@@ -8,9 +8,10 @@ module Kameleoon
8
8
  module Targeting
9
9
  class Segment
10
10
  include TreeBuilder
11
- attr_accessor :id, :tree
11
+ attr_accessor :id, :tree, :audience_tracking
12
+
12
13
  def to_s
13
- @tree.to_s
14
+ "Segment{id:#{@id},audience_tracking:#{@audience_tracking}}"
14
15
  end
15
16
 
16
17
  def initialize(*args)
@@ -27,6 +28,7 @@ module Kameleoon
27
28
  if hash['conditionsData'].nil?
28
29
  raise Kameleoon::Exception::NotFound.new(hash['conditionsData']), 'hash[\'conditionsData\']'
29
30
  end
31
+ @audience_tracking = hash['audienceTracking'] || false
30
32
  @tree = create_tree(hash['conditionsData'])
31
33
  elsif args.length == 2
32
34
  @id = args[0]
@@ -84,13 +86,13 @@ module Kameleoon
84
86
  # Computing results
85
87
  if is_left_child_targeted.nil?
86
88
  if is_right_child_targeted == @or_operator
87
- is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
89
+ is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
88
90
  else
89
91
  is_targeted = nil
90
92
  end
91
93
  else
92
94
  if is_left_child_targeted == @or_operator
93
- is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
95
+ is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
94
96
  else
95
97
  if is_right_child_targeted == true
96
98
  is_targeted = true
@@ -102,7 +104,7 @@ module Kameleoon
102
104
  end
103
105
  end
104
106
  end
105
- Marshal.load(Marshal.dump(is_targeted)) #Deep copy
107
+ Marshal.load(Marshal.dump(is_targeted)) # Deep copy
106
108
  end
107
109
 
108
110
  def check_condition(datas, condition = @condition)
@@ -19,25 +19,19 @@ module Kameleoon
19
19
  @visitor_manager = visitor_manager
20
20
  end
21
21
 
22
- def check_targeting(visitor_code, campaign_id, exp_ff_rule)
22
+ def check_targeting(visitor_code, campaign_id, segment)
23
23
  Logging::KameleoonLogger.debug(
24
- "CALL: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s)",
25
- visitor_code, campaign_id, exp_ff_rule
24
+ "CALL: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, segment: %s)",
25
+ visitor_code, campaign_id, segment
26
26
  )
27
- segment = exp_ff_rule.targeting_segment
28
- if segment.nil?
29
- Logging::KameleoonLogger.debug(
30
- "RETURN: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s) -> " \
31
- '(targeting: true)', visitor_code, campaign_id, exp_ff_rule
32
- )
33
- return true
27
+ targeting = true
28
+ unless segment.nil?
29
+ visitor = @visitor_manager.get_visitor(visitor_code)
30
+ targeting = segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
34
31
  end
35
-
36
- visitor = @visitor_manager.get_visitor(visitor_code)
37
- targeting = segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
38
32
  Logging::KameleoonLogger.debug(
39
- "RETURN: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s) -> " \
40
- '(targeting: %s)', visitor_code, campaign_id, exp_ff_rule, targeting
33
+ "RETURN: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, segment: %s) -> " \
34
+ '(targeting: %s)', visitor_code, campaign_id, segment, targeting
41
35
  )
42
36
  targeting
43
37
  end
@@ -86,7 +80,9 @@ module Kameleoon
86
80
  when ConditionType::TARGET_PERSONALIZATION
87
81
  condition_data = TargetPersonalizationInfo.new(visitor&.personalizations)
88
82
  when ConditionType::EXCLUSIVE_EXPERIMENT
89
- condition_data = ExclusiveExperimentInfo.new(campaign_id, visitor&.variations, visitor&.personalizations)
83
+ unless campaign_id.nil?
84
+ condition_data = ExclusiveExperimentInfo.new(campaign_id, visitor&.variations, visitor&.personalizations)
85
+ end
90
86
  when ConditionType::FIRST_VISIT,
91
87
  ConditionType::LAST_VISIT,
92
88
  ConditionType::VISITS,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kameleoon
4
- SDK_VERSION = '3.13.0'
4
+ SDK_VERSION = '3.15.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.13.0
4
+ version: 3.15.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-05-26 00:00:00.000000000 Z
11
+ date: 2025-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request
@@ -108,6 +108,7 @@ files:
108
108
  - lib/kameleoon/data/operating_system.rb
109
109
  - lib/kameleoon/data/page_view.rb
110
110
  - lib/kameleoon/data/personalization.rb
111
+ - lib/kameleoon/data/targeted_segment.rb
111
112
  - lib/kameleoon/data/unique_identifier.rb
112
113
  - lib/kameleoon/data/user_agent.rb
113
114
  - lib/kameleoon/data/visitor_visits.rb