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 +4 -4
- data/lib/kameleoon/configuration/custom_data_info.rb +7 -1
- data/lib/kameleoon/configuration/data_file.rb +26 -12
- data/lib/kameleoon/configuration/feature_flag.rb +8 -4
- data/lib/kameleoon/configuration/rule.rb +5 -6
- data/lib/kameleoon/data/cookie.rb +0 -1
- data/lib/kameleoon/data/data.rb +1 -0
- data/lib/kameleoon/data/manager/visitor.rb +23 -1
- data/lib/kameleoon/data/targeted_segment.rb +30 -0
- data/lib/kameleoon/kameleoon_client.rb +51 -22
- data/lib/kameleoon/network/content_type.rb +1 -0
- data/lib/kameleoon/network/network_manager.rb +1 -1
- data/lib/kameleoon/network/url_provider.rb +1 -1
- data/lib/kameleoon/targeting/conditions/segment_condition.rb +3 -3
- data/lib/kameleoon/targeting/models.rb +7 -5
- data/lib/kameleoon/targeting/targeting_manager.rb +12 -16
- data/lib/kameleoon/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07240065fc16f98169c01fd4367321b2f633d79373f293b0bcf110f68d920d46
|
4
|
+
data.tar.gz: 52b83806f28aa0035eb8df2db89f84b98b774e2a53b0fd8a7c5abd71b0a59ac4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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, :
|
14
|
-
:
|
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
|
-
@
|
73
|
-
configuration['
|
74
|
-
|
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
|
-
|
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
|
-
@
|
50
|
-
@
|
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
|
|
data/lib/kameleoon/data/data.rb
CHANGED
@@ -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,
|
908
|
-
@targeting_manager.check_targeting(visitor_code, campaign_id,
|
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
|
-
|
1043
|
-
|
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
|
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
|
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?
|
@@ -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::
|
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
|
-
|
23
|
-
return false
|
22
|
+
segment = segment_info.data_file.segments[@segment_id]
|
23
|
+
return false if segment.nil?
|
24
24
|
|
25
|
-
|
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
|
-
@
|
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,
|
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,
|
25
|
-
visitor_code, campaign_id,
|
24
|
+
"CALL: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, segment: %s)",
|
25
|
+
visitor_code, campaign_id, segment
|
26
26
|
)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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,
|
40
|
-
'(targeting: %s)', visitor_code, campaign_id,
|
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
|
-
|
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,
|
data/lib/kameleoon/version.rb
CHANGED
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.
|
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-
|
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
|