kameleoon-client-ruby 3.13.0 → 3.14.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 +18 -6
- 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/kameleoon_client.rb +27 -19
- 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/models.rb +4 -3
- data/lib/kameleoon/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5566ec9ff02ebb63ff3ee337f4c26fc0c229f2de8df0893fe85dc3f83fbc0ce
|
4
|
+
data.tar.gz: fcd11bacbaead7a0c357a0fd4828fb6c4b7347ebf1fab728f4e00882b0224dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78418ebd561b9d1fe2639751d0dae68a875a4a76f4782ac079fe2f3f3c6e2b7466c41cb762e978b4895973a97ddf4940b10171d36c2695a565ed2e1b66445f53
|
7
|
+
data.tar.gz: b91b577c23c13547d78c09c98a2c39bb788a314b280b50c57bf1cc25891dd74747127aec2bc14b0a5a4908a2cde2d0e2089ec13c6f5200087b7c52bd80b7a68f
|
@@ -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,6 +6,7 @@ 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
|
@@ -69,18 +70,29 @@ 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 = parse_segments(configuration)
|
74
|
+
@custom_data_info = CustomDataInfo.new(configuration['customData'])
|
75
|
+
@feature_flags = parse_feature_flags(configuration, segments, @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
|
+
configuration['segments'].to_h do |raw_seg|
|
84
|
+
seg = Targeting::Segment.new(raw_seg)
|
85
|
+
[seg.id, seg]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_feature_flags(configuration, segments, cdi)
|
90
|
+
configuration['featureFlags'].to_h do |raw_ff|
|
91
|
+
ff = FeatureFlag.new(raw_ff, segments, cdi)
|
92
|
+
[ff.feature_key, ff]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
84
96
|
def any_targeted_delivery_rule?
|
85
97
|
@feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
|
86
98
|
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
|
|
@@ -933,7 +933,7 @@ module Kameleoon
|
|
933
933
|
forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
|
934
934
|
if forced_variation
|
935
935
|
eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
|
936
|
-
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save) && \
|
936
|
+
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save, feature_flag.bucketing_custom_data_index) && \
|
937
937
|
ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
|
938
938
|
eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
939
939
|
end
|
@@ -955,7 +955,7 @@ module Kameleoon
|
|
955
955
|
unrestricted = true
|
956
956
|
me_group = @data_manager.data_file.me_groups[feature_flag.me_group_name]
|
957
957
|
if me_group
|
958
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
958
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
959
959
|
me_group_hash = Utils::Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
|
960
960
|
Logging::KameleoonLogger.debug(
|
961
961
|
"Calculated ME group hash %s for code: '%s', meGroup: '%s'",
|
@@ -970,17 +970,17 @@ module Kameleoon
|
|
970
970
|
unrestricted
|
971
971
|
end
|
972
972
|
|
973
|
-
def visitor_not_in_holdout?(visitor, visitor_code, track, save)
|
973
|
+
def visitor_not_in_holdout?(visitor, visitor_code, track, save, bucketing_custom_data_index)
|
974
974
|
holdout = @data_manager.data_file.holdout
|
975
975
|
return true if holdout.nil?
|
976
976
|
|
977
977
|
in_holdout_variation_key = 'in-holdout'
|
978
978
|
Logging::KameleoonLogger.debug(
|
979
|
-
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s
|
980
|
-
visitor_code, track, save
|
979
|
+
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
|
980
|
+
'bucketing_custom_data_index: %s)', visitor_code, track, save, bucketing_custom_data_index
|
981
981
|
)
|
982
982
|
is_not_in_holdout = true
|
983
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
983
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
984
984
|
variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
|
985
985
|
Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
|
986
986
|
var_by_exp = holdout.get_variation(variation_hash)
|
@@ -992,9 +992,9 @@ module Kameleoon
|
|
992
992
|
end
|
993
993
|
end
|
994
994
|
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
|
995
|
+
"RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
|
996
|
+
'bucketing_custom_data_index: %s) -> (is_not_in_holdout: %s)',
|
997
|
+
visitor_code, track, save, bucketing_custom_data_index, is_not_in_holdout
|
998
998
|
)
|
999
999
|
is_not_in_holdout
|
1000
1000
|
end
|
@@ -1038,16 +1038,24 @@ module Kameleoon
|
|
1038
1038
|
)
|
1039
1039
|
end
|
1040
1040
|
|
1041
|
-
def get_code_for_hash(visitor, visitor_code)
|
1042
|
-
|
1043
|
-
|
1041
|
+
def get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
1042
|
+
return visitor_code if visitor.nil?
|
1043
|
+
|
1044
|
+
# 1. Try to use the bucketing custom data's value if bucketingCustomDataId is defined
|
1045
|
+
if bucketing_custom_data_index
|
1046
|
+
bucketing_custom_data = visitor.custom_data.get(bucketing_custom_data_index)
|
1047
|
+
return bucketing_custom_data.values[0] if bucketing_custom_data && !bucketing_custom_data.values.empty?
|
1048
|
+
end
|
1049
|
+
# 2. Use mappingIdentifier instead of visitorCode if it was set up
|
1050
|
+
visitor.mapping_identifier || visitor_code
|
1044
1051
|
end
|
1045
1052
|
|
1046
|
-
def evaluate_cbscores(visitor, visitor_code, rule)
|
1053
|
+
def evaluate_cbscores(visitor, visitor_code, rule, bucketing_custom_data_index)
|
1047
1054
|
return nil if visitor&.cbscores.nil?
|
1048
1055
|
|
1049
1056
|
Logging::KameleoonLogger.debug(
|
1050
|
-
"CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s
|
1057
|
+
"CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s, " \
|
1058
|
+
'bucketing_custom_data_index: %s)', visitor_code, rule, bucketing_custom_data_index
|
1051
1059
|
)
|
1052
1060
|
eval_exp = nil
|
1053
1061
|
var_id_group_by_scores = visitor.cbscores.values[rule.experiment.id]
|
@@ -1062,7 +1070,7 @@ module Kameleoon
|
|
1062
1070
|
if (var_by_exp_in_cbs&.size || 0).positive?
|
1063
1071
|
size = var_by_exp_in_cbs.size
|
1064
1072
|
if size > 1
|
1065
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
1073
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
1066
1074
|
variation_hash = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
|
1067
1075
|
Logging::KameleoonLogger.debug("Calculated CBS hash %s for code '%s'", variation_hash, code_for_hash)
|
1068
1076
|
idx = [(variation_hash * size).to_i, size - 1].min
|
@@ -1073,8 +1081,8 @@ module Kameleoon
|
|
1073
1081
|
end
|
1074
1082
|
end
|
1075
1083
|
Logging::KameleoonLogger.debug(
|
1076
|
-
"RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s
|
1077
|
-
visitor_code, rule, eval_exp
|
1084
|
+
"RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s," \
|
1085
|
+
'bucketing_custom_data_index: %s) -> (eval_exp: %s)', visitor_code, rule, bucketing_custom_data_index, eval_exp
|
1078
1086
|
)
|
1079
1087
|
eval_exp
|
1080
1088
|
end
|
@@ -1087,7 +1095,7 @@ module Kameleoon
|
|
1087
1095
|
visitor_code, feature_flag
|
1088
1096
|
)
|
1089
1097
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
1090
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
1098
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
1091
1099
|
# no rules -> return default_variation_key
|
1092
1100
|
eval_exp = nil
|
1093
1101
|
feature_flag.rules.each do |rule|
|
@@ -1110,7 +1118,7 @@ module Kameleoon
|
|
1110
1118
|
Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
|
1111
1119
|
# check main expostion for rule with hashRule
|
1112
1120
|
if hash_rule <= rule.exposition
|
1113
|
-
eval_exp = evaluate_cbscores(visitor, visitor_code, rule)
|
1121
|
+
eval_exp = evaluate_cbscores(visitor, visitor_code, rule, feature_flag.bucketing_custom_data_index)
|
1114
1122
|
break unless eval_exp.nil?
|
1115
1123
|
|
1116
1124
|
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'
|
@@ -9,6 +9,7 @@ module Kameleoon
|
|
9
9
|
class Segment
|
10
10
|
include TreeBuilder
|
11
11
|
attr_accessor :id, :tree
|
12
|
+
|
12
13
|
def to_s
|
13
14
|
@tree.to_s
|
14
15
|
end
|
@@ -84,13 +85,13 @@ module Kameleoon
|
|
84
85
|
# Computing results
|
85
86
|
if is_left_child_targeted.nil?
|
86
87
|
if is_right_child_targeted == @or_operator
|
87
|
-
is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
|
88
|
+
is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
|
88
89
|
else
|
89
90
|
is_targeted = nil
|
90
91
|
end
|
91
92
|
else
|
92
93
|
if is_left_child_targeted == @or_operator
|
93
|
-
is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
|
94
|
+
is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
|
94
95
|
else
|
95
96
|
if is_right_child_targeted == true
|
96
97
|
is_targeted = true
|
@@ -102,7 +103,7 @@ module Kameleoon
|
|
102
103
|
end
|
103
104
|
end
|
104
105
|
end
|
105
|
-
Marshal.load(Marshal.dump(is_targeted)) #Deep copy
|
106
|
+
Marshal.load(Marshal.dump(is_targeted)) # Deep copy
|
106
107
|
end
|
107
108
|
|
108
109
|
def check_condition(datas, condition = @condition)
|
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.14.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-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|