kameleoon-client-ruby 3.7.0 → 3.9.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/data_file.rb +28 -7
- data/lib/kameleoon/configuration/experiment.rb +46 -0
- data/lib/kameleoon/configuration/feature_flag.rb +7 -5
- data/lib/kameleoon/configuration/me_group.rb +19 -0
- data/lib/kameleoon/configuration/rule.rb +3 -30
- data/lib/kameleoon/data/manager/visitor.rb +1 -1
- data/lib/kameleoon/kameleoon_client.rb +166 -93
- data/lib/kameleoon/network/cookie/cookie_manager.rb +2 -2
- data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +2 -3
- data/lib/kameleoon/targeting/targeting_manager.rb +3 -3
- data/lib/kameleoon/utils.rb +11 -14
- data/lib/kameleoon/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d0ea98aa7ca7ea1585f83f6bd231aa577a774124b4332d18ca4e2c2649bfbc2
|
4
|
+
data.tar.gz: ec43717ed1dd3a61828a9c062f10040a7e214e070f30f6deb2a76bec9d60fb76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bec6bd1d823d247b5b1e561e621f454e4d451bec12fb1312dc2a9f348f1e2ccbcc884e6927bae254da107e6314e4d91acad9bb1ef67012d96b62a34253f2978f
|
7
|
+
data.tar.gz: d5f5c2c4d85b5d2f706d35fc77f3bc5ce8b6e24ce56413e5d9086fd95aae0d8adf1bbe37a6b5d041f389575db0c8c3815e005a78a7a9d45f255a9325b4f11c46
|
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'kameleoon/configuration/settings'
|
4
|
-
require 'kameleoon/configuration/feature_flag'
|
5
3
|
require 'kameleoon/configuration/custom_data_info'
|
4
|
+
require 'kameleoon/configuration/experiment'
|
5
|
+
require 'kameleoon/configuration/feature_flag'
|
6
|
+
require 'kameleoon/configuration/me_group'
|
7
|
+
require 'kameleoon/configuration/settings'
|
6
8
|
require 'kameleoon/logging/kameleoon_logger'
|
7
9
|
|
8
10
|
module Kameleoon
|
9
11
|
module Configuration
|
10
12
|
class DataFile
|
11
|
-
attr_reader :settings, :feature_flags, :
|
12
|
-
:rule_info_by_exp_id, :variation_by_id, :custom_data_info,
|
13
|
+
attr_reader :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule, :feature_flag_by_id,
|
14
|
+
:rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
|
15
|
+
:experiment_ids_with_js_css_variable, :holdout
|
13
16
|
|
14
17
|
def to_s
|
15
18
|
'DataFile{' \
|
@@ -49,6 +52,7 @@ module Kameleoon
|
|
49
52
|
Logging::KameleoonLogger.debug('CALL: DataFile.init_default')
|
50
53
|
@settings = Settings.new
|
51
54
|
@feature_flags = {}
|
55
|
+
@me_groups = {}
|
52
56
|
@has_any_targeted_delivery_rule = false
|
53
57
|
@custom_data_info = Kameleoon::Configuration::CustomDataInfo.new(nil)
|
54
58
|
Logging::KameleoonLogger.debug('RETURN: DataFile.init_default')
|
@@ -62,8 +66,10 @@ module Kameleoon
|
|
62
66
|
ff = FeatureFlag.new(raw)
|
63
67
|
@feature_flags[ff.feature_key] = ff
|
64
68
|
end
|
69
|
+
@me_groups = make_me_groups(@feature_flags)
|
65
70
|
@has_any_targeted_delivery_rule = any_targeted_delivery_rule?
|
66
71
|
@custom_data_info = CustomDataInfo.new(configuration['customData'])
|
72
|
+
@holdout = Experiment.from_json(configuration['holdout']) if configuration.include?('holdout')
|
67
73
|
Logging::KameleoonLogger.debug('RETURN: DataFile.init(configuration: %s)', configuration)
|
68
74
|
end
|
69
75
|
|
@@ -85,11 +91,11 @@ module Kameleoon
|
|
85
91
|
has_feature_flag_variable_js_css = feature_flag_variable_js_css?(feature_flag)
|
86
92
|
feature_flag.rules.each do |rule|
|
87
93
|
@rule_by_segment_id[rule.segment_id] = rule
|
88
|
-
@rule_info_by_exp_id[rule.
|
89
|
-
rule.
|
94
|
+
@rule_info_by_exp_id[rule.experiment.id] = RuleInfo.new(feature_flag, rule)
|
95
|
+
rule.experiment.variations_by_exposition.each do |variation|
|
90
96
|
@variation_by_id[variation.variation_id] = variation
|
91
97
|
end
|
92
|
-
@experiment_ids_with_js_css_variable.add(rule.
|
98
|
+
@experiment_ids_with_js_css_variable.add(rule.experiment.id) if has_feature_flag_variable_js_css
|
93
99
|
end
|
94
100
|
end
|
95
101
|
@feature_flag_by_id.freeze
|
@@ -98,6 +104,21 @@ module Kameleoon
|
|
98
104
|
@experiment_ids_with_js_css_variable.freeze
|
99
105
|
end
|
100
106
|
|
107
|
+
def make_me_groups(feature_flags)
|
108
|
+
me_group_lists = {}
|
109
|
+
feature_flags.each_value do |feature_flag|
|
110
|
+
next if feature_flag.me_group_name.nil?
|
111
|
+
|
112
|
+
me_group_list = me_group_lists[feature_flag.me_group_name]
|
113
|
+
if me_group_list
|
114
|
+
me_group_list.push(feature_flag)
|
115
|
+
else
|
116
|
+
me_group_lists[feature_flag.me_group_name] = [feature_flag]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
me_group_lists.transform_values { |me_group_list| MEGroup.new(me_group_list) }
|
120
|
+
end
|
121
|
+
|
101
122
|
def feature_flag_variable_js_css?(feature_flag)
|
102
123
|
feature_flag.variations.first&.variables&.any? do |variable|
|
103
124
|
%w[JS CSS].include?(variable.type)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'variation_exposition'
|
4
|
+
require 'kameleoon/exceptions'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
module Configuration
|
8
|
+
class Experiment
|
9
|
+
attr_reader :id, :variations_by_exposition
|
10
|
+
|
11
|
+
def initialize(id, variations_by_exposition)
|
12
|
+
@id = id
|
13
|
+
@variations_by_exposition = variations_by_exposition
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_json(hash)
|
17
|
+
id = hash['experimentId'] || 0
|
18
|
+
variations_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
|
19
|
+
variations_by_exposition.freeze
|
20
|
+
Experiment.new(id, variations_by_exposition)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Experiment{id:#{@id}}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_variation(hash_double)
|
28
|
+
total = 0.0
|
29
|
+
@variations_by_exposition.each do |var_by_exp|
|
30
|
+
total += var_by_exp.exposition
|
31
|
+
return var_by_exp if total >= hash_double
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_variation_by_key(variation_key)
|
37
|
+
var_by_exp = @variations_by_exposition.find { |v| v.variation_key == variation_key }
|
38
|
+
unless var_by_exp
|
39
|
+
raise Exception::FeatureVariationNotFound.new(variation_key),
|
40
|
+
"#{self} does not contain variation '#{variation_key}'"
|
41
|
+
end
|
42
|
+
var_by_exp
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -8,7 +8,7 @@ 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, :environment_enabled, :rules
|
11
|
+
attr_accessor :id, :feature_key, :variations, :default_variation_key, :me_group_name, :environment_enabled, :rules
|
12
12
|
|
13
13
|
def self.create_from_array(array)
|
14
14
|
array&.map { |it| FeatureFlag.new(it) }
|
@@ -16,10 +16,11 @@ module Kameleoon
|
|
16
16
|
|
17
17
|
def to_s
|
18
18
|
'FeatureFlag{' \
|
19
|
-
"id:#{@id},
|
20
|
-
"feature_key
|
21
|
-
"environment_enabled:#{@environment_enabled},
|
22
|
-
"default_variation_key
|
19
|
+
"id:#{@id}," \
|
20
|
+
"feature_key:'#{@feature_key}'," \
|
21
|
+
"environment_enabled:#{@environment_enabled}," \
|
22
|
+
"default_variation_key:'#{@default_variation_key}'," \
|
23
|
+
"me_group_name:'#{@me_group_name}'," \
|
23
24
|
"rules:#{@rules.size}" \
|
24
25
|
'}'
|
25
26
|
end
|
@@ -29,6 +30,7 @@ module Kameleoon
|
|
29
30
|
@feature_key = hash['featureKey']
|
30
31
|
@variations = Variation.create_from_array(hash['variations'])
|
31
32
|
@default_variation_key = hash['defaultVariationKey']
|
33
|
+
@me_group_name = hash['mutuallyExclusiveGroup']
|
32
34
|
@environment_enabled = hash['environmentEnabled']
|
33
35
|
@rules = Rule.create_from_array(hash['rules'])
|
34
36
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Configuration
|
5
|
+
class MEGroup
|
6
|
+
attr_reader :feature_flags
|
7
|
+
|
8
|
+
def initialize(feature_flags)
|
9
|
+
@feature_flags = feature_flags.sort { |a, b| a.id <=> b.id }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_feature_flag_by_hash(hash)
|
13
|
+
idx = (hash * @feature_flags.size).to_i
|
14
|
+
idx = @feature_flags.size - 1 if idx >= @feature_flags.size
|
15
|
+
@feature_flags[idx]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require 'kameleoon/exceptions'
|
3
|
+
require_relative 'experiment'
|
5
4
|
require 'kameleoon/targeting/models'
|
6
5
|
|
7
6
|
module Kameleoon
|
@@ -30,8 +29,7 @@ module Kameleoon
|
|
30
29
|
|
31
30
|
# Rule is a class for new rules of feature flags
|
32
31
|
class Rule
|
33
|
-
attr_reader :id, :order, :type, :exposition, :
|
34
|
-
:first_variation
|
32
|
+
attr_reader :id, :order, :type, :exposition, :experiment, :respool_time, :segment_id
|
35
33
|
attr_accessor :targeting_segment
|
36
34
|
|
37
35
|
def self.create_from_array(array)
|
@@ -47,35 +45,10 @@ module Kameleoon
|
|
47
45
|
@order = hash['order']
|
48
46
|
@type = RuleType.from_literal(hash['type'])
|
49
47
|
@exposition = hash['exposition']
|
50
|
-
@experiment_id = hash['experimentId']
|
51
48
|
@respool_time = hash['respoolTime']
|
52
|
-
@variation_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
|
53
|
-
@variation_by_exposition.freeze
|
54
|
-
@first_variation = @variation_by_exposition.first
|
55
49
|
@targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
|
56
50
|
@segment_id = @targeting_segment != nil ? targeting_segment.id : -1
|
57
|
-
|
58
|
-
|
59
|
-
def get_variation(hash_double)
|
60
|
-
total = 0.0
|
61
|
-
variation_by_exposition.each do |var_by_exp|
|
62
|
-
total += var_by_exp.exposition
|
63
|
-
return var_by_exp if total >= hash_double
|
64
|
-
end
|
65
|
-
nil
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_variation_by_key(variation_key)
|
69
|
-
var_by_exp = variation_by_exposition.find { |v| v.variation_key == variation_key }
|
70
|
-
unless var_by_exp
|
71
|
-
raise Exception::FeatureVariationNotFound.new(variation_key),
|
72
|
-
"#{self} does not contain variation '#{variation_key}'"
|
73
|
-
end
|
74
|
-
var_by_exp
|
75
|
-
end
|
76
|
-
|
77
|
-
def get_variation_id_by_key(key)
|
78
|
-
variation_by_exposition.select { |v| v.variation_key == key }.first&.variation_id
|
51
|
+
@experiment = Experiment.from_json(hash)
|
79
52
|
end
|
80
53
|
|
81
54
|
def experimentation_type?
|
@@ -405,7 +405,7 @@ module Kameleoon
|
|
405
405
|
|
406
406
|
def add_forced_experiment_variation(forced_variation)
|
407
407
|
@forced_variations ||= {}
|
408
|
-
@forced_variations[forced_variation.rule.
|
408
|
+
@forced_variations[forced_variation.rule.experiment.id] = forced_variation
|
409
409
|
end
|
410
410
|
end
|
411
411
|
end
|
@@ -321,9 +321,9 @@ module Kameleoon
|
|
321
321
|
)
|
322
322
|
Utils::VisitorCode.validate(visitor_code)
|
323
323
|
feature_flag = @data_manager.data_file.get_feature_flag(feature_key)
|
324
|
-
variation_key,
|
324
|
+
variation_key, eval_exp = get_variation_info(visitor_code, feature_flag, track)
|
325
325
|
variation = feature_flag.get_variation_by_key(variation_key)
|
326
|
-
external_variation =
|
326
|
+
external_variation = create_external_variation(variation, eval_exp)
|
327
327
|
@tracking_manager.add_visitor_code(visitor_code) if track
|
328
328
|
Logging::KameleoonLogger.info(
|
329
329
|
"RETURN: KameleoonClient.get_variation(visitor_code: '%s', feature_key: '%s', track: %s)" \
|
@@ -358,12 +358,11 @@ module Kameleoon
|
|
358
358
|
@data_manager.data_file.feature_flags.each_value do |feature_flag|
|
359
359
|
next unless feature_flag.environment_enabled
|
360
360
|
|
361
|
-
variation_key,
|
361
|
+
variation_key, eval_exp = get_variation_info(visitor_code, feature_flag, track)
|
362
362
|
next if only_active && (variation_key == Configuration::VariationType::VARIATION_OFF)
|
363
363
|
|
364
364
|
variation = feature_flag.get_variation_by_key(variation_key)
|
365
|
-
variations[feature_flag.feature_key] =
|
366
|
-
make_external_variation(variation, var_by_exp&.variation_id, rule&.experiment_id)
|
365
|
+
variations[feature_flag.feature_key] = create_external_variation(variation, eval_exp)
|
367
366
|
end
|
368
367
|
variations.freeze
|
369
368
|
@tracking_manager.add_visitor_code(visitor_code) if track
|
@@ -624,14 +623,8 @@ module Kameleoon
|
|
624
623
|
@data_manager.data_file.feature_flags.each do |feature_key, feature_flag|
|
625
624
|
next unless feature_flag.environment_enabled
|
626
625
|
|
627
|
-
|
628
|
-
|
629
|
-
variation = forced_variation.var_by_exp
|
630
|
-
rule = forced_variation.rule
|
631
|
-
else
|
632
|
-
variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
633
|
-
end
|
634
|
-
variation_key = _get_variation_key(variation, rule, feature_flag)
|
626
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, false, false)
|
627
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
635
628
|
list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
|
636
629
|
end
|
637
630
|
Logging::KameleoonLogger.info(
|
@@ -665,20 +658,13 @@ module Kameleoon
|
|
665
658
|
@data_manager.data_file.feature_flags.each_value do |feature_flag|
|
666
659
|
next unless feature_flag.environment_enabled
|
667
660
|
|
668
|
-
|
669
|
-
|
670
|
-
var_by_exp = forced_variation.var_by_exp
|
671
|
-
rule = forced_variation.rule
|
672
|
-
else
|
673
|
-
var_by_exp, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
674
|
-
end
|
675
|
-
variation_key = _get_variation_key(var_by_exp, rule, feature_flag)
|
661
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, false, false)
|
662
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
676
663
|
|
677
664
|
next if variation_key == Configuration::VariationType::VARIATION_OFF
|
678
665
|
|
679
666
|
variation = feature_flag.get_variation_by_key(variation_key)
|
680
|
-
map_active_features[feature_flag.feature_key] =
|
681
|
-
make_external_variation(variation, var_by_exp&.variation_id, rule&.experiment_id)
|
667
|
+
map_active_features[feature_flag.feature_key] = create_external_variation(variation, eval_exp)
|
682
668
|
end
|
683
669
|
|
684
670
|
map_active_features.freeze
|
@@ -753,7 +739,7 @@ module Kameleoon
|
|
753
739
|
raise Exception::FeatureExperimentNotFound.new(experiment_id), "Experiment #{experiment_id} is not found"
|
754
740
|
end
|
755
741
|
|
756
|
-
var_by_exp = rule_info.rule.get_variation_by_key(variation_key)
|
742
|
+
var_by_exp = rule_info.rule.experiment.get_variation_by_key(variation_key)
|
757
743
|
forced_variation = DataManager::ForcedExperimentVariation.new(rule_info.rule, var_by_exp, force_targeting)
|
758
744
|
@visitor_manager.add_data(visitor_code, forced_variation)
|
759
745
|
end
|
@@ -917,23 +903,89 @@ module Kameleoon
|
|
917
903
|
visitor_code, feature_flag, track
|
918
904
|
)
|
919
905
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
906
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, track, true)
|
907
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
908
|
+
Logging::KameleoonLogger.debug(
|
909
|
+
"RETURN: KameleoonClient.get_variation_info(visitor_code: '%s', feature_flag: %s, track: %s)" \
|
910
|
+
' -> (variation_key: %s, eval_exp: %s)',
|
911
|
+
visitor_code, feature_flag, track, variation_key, eval_exp
|
912
|
+
)
|
913
|
+
[variation_key, eval_exp]
|
914
|
+
end
|
915
|
+
|
916
|
+
def evaluate(visitor, visitor_code, feature_flag, track, save)
|
917
|
+
Logging::KameleoonLogger.debug(
|
918
|
+
"CALL: KameleoonClient.evaluate(visitor, visitor_code: '%s', feature_flag: %s, track: %s, save: %s)",
|
919
|
+
visitor_code, feature_flag, track, save
|
920
|
+
)
|
921
|
+
eval_exp = nil
|
920
922
|
forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
|
921
923
|
if forced_variation
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
924
|
+
eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
|
925
|
+
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save) && \
|
926
|
+
ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
|
927
|
+
eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
926
928
|
end
|
927
|
-
|
928
|
-
|
929
|
+
save_variation(visitor_code, eval_exp, track: track) if save && !forced_variation&.simulated
|
930
|
+
Logging::KameleoonLogger.debug(
|
931
|
+
"RETURN: KameleoonClient.evaluate(visitor, visitor_code: '%s', feature_flag: %s, track: %s, save: %s)" \
|
932
|
+
' -> (eval_exp: %s)', visitor_code, feature_flag, track, save, eval_exp
|
933
|
+
)
|
934
|
+
eval_exp
|
935
|
+
end
|
936
|
+
|
937
|
+
def ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
|
938
|
+
return true if feature_flag.me_group_name.nil?
|
939
|
+
|
940
|
+
Logging::KameleoonLogger.debug(
|
941
|
+
"CALL: KameleoonClient.ff_unrestricted_by_me_group?(visitor, visitor_code: '%s', feature_flag: %s)",
|
942
|
+
visitor_code, feature_flag
|
943
|
+
)
|
944
|
+
unrestricted = true
|
945
|
+
me_group = @data_manager.data_file.me_groups[feature_flag.me_group_name]
|
946
|
+
if me_group
|
947
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
948
|
+
me_group_hash = Utils::Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
|
949
|
+
Logging::KameleoonLogger.debug(
|
950
|
+
"Calculated ME group hash %s for code: '%s', meGroup: '%s'",
|
951
|
+
me_group_hash, code_for_hash, feature_flag.me_group_name
|
952
|
+
)
|
953
|
+
unrestricted = me_group.get_feature_flag_by_hash(me_group_hash) == feature_flag
|
929
954
|
end
|
930
|
-
variation_key = _get_variation_key(var_by_exp, rule, feature_flag)
|
931
955
|
Logging::KameleoonLogger.debug(
|
932
|
-
"RETURN: KameleoonClient.
|
933
|
-
' -> (
|
934
|
-
visitor_code, feature_flag, track, variation_key, var_by_exp, rule
|
956
|
+
"RETURN: KameleoonClient.ff_unrestricted_by_me_group?(visitor, visitor_code: '%s', feature_flag: %s)" \
|
957
|
+
' -> (unrestricted: %s)', visitor_code, feature_flag, unrestricted
|
935
958
|
)
|
936
|
-
|
959
|
+
unrestricted
|
960
|
+
end
|
961
|
+
|
962
|
+
def visitor_not_in_holdout?(visitor, visitor_code, track, save)
|
963
|
+
holdout = @data_manager.data_file.holdout
|
964
|
+
return true if holdout.nil?
|
965
|
+
|
966
|
+
in_holdout_variation_key = 'in-holdout'
|
967
|
+
Logging::KameleoonLogger.debug(
|
968
|
+
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)",
|
969
|
+
visitor_code, track, save
|
970
|
+
)
|
971
|
+
is_not_in_holdout = true
|
972
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
973
|
+
variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
|
974
|
+
Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
|
975
|
+
var_by_exp = holdout.get_variation(variation_hash)
|
976
|
+
unless var_by_exp.nil?
|
977
|
+
is_not_in_holdout = var_by_exp.variation_key != in_holdout_variation_key
|
978
|
+
if save
|
979
|
+
eval_exp = EvaluatedExperiment.new(var_by_exp, holdout, Configuration::RuleType::EXPERIMENTATION)
|
980
|
+
save_variation(visitor_code, eval_exp, track: track)
|
981
|
+
end
|
982
|
+
end
|
983
|
+
Logging::KameleoonLogger.debug(
|
984
|
+
"RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)" \
|
985
|
+
' -> (is_not_in_holdout: %s)',
|
986
|
+
visitor_code, track, save, is_not_in_holdout
|
987
|
+
)
|
988
|
+
is_not_in_holdout
|
937
989
|
end
|
938
990
|
|
939
991
|
##
|
@@ -945,15 +997,8 @@ module Kameleoon
|
|
945
997
|
)
|
946
998
|
feature_flag = @data_manager.data_file.get_feature_flag(feature_key)
|
947
999
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
948
|
-
|
949
|
-
|
950
|
-
variation = forced_variation.var_by_exp
|
951
|
-
rule = forced_variation.rule
|
952
|
-
else
|
953
|
-
variation, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
954
|
-
end
|
955
|
-
save_variation(visitor_code, rule, variation) unless forced_variation&.simulated
|
956
|
-
variation_key = _get_variation_key(variation, rule, feature_flag)
|
1000
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, true, true)
|
1001
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
957
1002
|
@tracking_manager.add_visitor_code(visitor_code)
|
958
1003
|
Logging::KameleoonLogger.debug(
|
959
1004
|
"RETURN: KameleoonClient._get_feature_variation_key(visitor_code: '%s', feature_key: '%s')" \
|
@@ -963,25 +1008,30 @@ module Kameleoon
|
|
963
1008
|
[feature_flag, variation_key]
|
964
1009
|
end
|
965
1010
|
|
966
|
-
def save_variation(visitor_code,
|
967
|
-
|
968
|
-
variation_id = var_by_exp&.variation_id
|
969
|
-
return if experiment_id.nil? || variation_id.nil?
|
1011
|
+
def save_variation(visitor_code, eval_exp, track: true)
|
1012
|
+
return if eval_exp.nil? || eval_exp.experiment.id.zero? || eval_exp.var_by_exp.variation_id.nil?
|
970
1013
|
|
971
1014
|
Logging::KameleoonLogger.debug(
|
972
|
-
"CALL: KameleoonClient.save_variation(visitor_code: '%s',
|
973
|
-
visitor_code,
|
1015
|
+
"CALL: KameleoonClient.save_variation(visitor_code: '%s', eval_exp: %s, track: %s)",
|
1016
|
+
visitor_code, eval_exp, track
|
974
1017
|
)
|
975
1018
|
visitor = @visitor_manager.get_or_create_visitor(visitor_code)
|
976
|
-
as_variation = Kameleoon::DataManager::AssignedVariation.new(
|
1019
|
+
as_variation = Kameleoon::DataManager::AssignedVariation.new(
|
1020
|
+
eval_exp.experiment.id, eval_exp.var_by_exp.variation_id, eval_exp.rule_type
|
1021
|
+
)
|
977
1022
|
as_variation.mark_as_sent unless track
|
978
1023
|
visitor.assign_variation(as_variation)
|
979
1024
|
Logging::KameleoonLogger.debug(
|
980
|
-
"RETURN: KameleoonClient.save_variation(visitor_code: '%s',
|
981
|
-
visitor_code,
|
1025
|
+
"RETURN: KameleoonClient.save_variation(visitor_code: '%s', eval_exp: %s, track: %s)",
|
1026
|
+
visitor_code, eval_exp, track
|
982
1027
|
)
|
983
1028
|
end
|
984
1029
|
|
1030
|
+
def get_code_for_hash(visitor, visitor_code)
|
1031
|
+
# use mappingIdentifier instead of visitor_code if it was set up
|
1032
|
+
visitor&.mapping_identifier || visitor_code
|
1033
|
+
end
|
1034
|
+
|
985
1035
|
##
|
986
1036
|
# helper method for calculate variation key for feature flag
|
987
1037
|
def _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
@@ -990,48 +1040,40 @@ module Kameleoon
|
|
990
1040
|
visitor_code, feature_flag
|
991
1041
|
)
|
992
1042
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
1043
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
993
1044
|
# no rules -> return default_variation_key
|
994
|
-
|
995
|
-
selected_rule = nil
|
1045
|
+
selected = nil
|
996
1046
|
feature_flag.rules.each do |rule|
|
997
|
-
forced_variation = visitor&.get_forced_experiment_variation(rule.
|
1047
|
+
forced_variation = visitor&.get_forced_experiment_variation(rule.experiment.id)
|
998
1048
|
if forced_variation&.force_targeting
|
999
1049
|
# Forcing experiment variation in force-targeting mode
|
1000
|
-
|
1001
|
-
selected_rule = rule
|
1050
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
|
1002
1051
|
break
|
1003
1052
|
end
|
1004
1053
|
# check if visitor is targeted for rule, else next rule
|
1005
|
-
next unless check_targeting(visitor_code, rule.
|
1054
|
+
next unless check_targeting(visitor_code, rule.experiment.id, rule)
|
1006
1055
|
|
1007
1056
|
unless forced_variation.nil?
|
1008
1057
|
# Forcing experiment variation in targeting-only mode
|
1009
|
-
|
1010
|
-
selected_rule = rule
|
1058
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
|
1011
1059
|
break
|
1012
1060
|
end
|
1013
|
-
# use mappingIdentifier instead of visitorCode if it was set up
|
1014
|
-
code_for_hash = visitor&.mapping_identifier || visitor_code
|
1015
1061
|
# uses for rule exposition
|
1016
|
-
hash_rule = Utils::
|
1017
|
-
Logging::KameleoonLogger.debug("Calculated
|
1062
|
+
hash_rule = Utils::Hasher.obtain(code_for_hash, rule.id, rule.respool_time)
|
1063
|
+
Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
|
1018
1064
|
# check main expostion for rule with hashRule
|
1019
1065
|
if hash_rule <= rule.exposition
|
1020
1066
|
if rule.targeted_delivery_type?
|
1021
|
-
|
1022
|
-
selected_rule = rule
|
1067
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(rule.experiment.variations_by_exposition.first, rule)
|
1023
1068
|
break
|
1024
1069
|
end
|
1025
1070
|
# uses for variation's expositions
|
1026
|
-
hash_variation = Utils::
|
1027
|
-
Logging::KameleoonLogger.debug(
|
1028
|
-
"Calculated hash_variation: %s for visitor_code: '%s'", hash_variation, code_for_hash
|
1029
|
-
)
|
1071
|
+
hash_variation = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
|
1072
|
+
Logging::KameleoonLogger.debug("Calculated variation hash %s for code '%s'", hash_variation, code_for_hash)
|
1030
1073
|
# get variation key with new hashVariation
|
1031
|
-
variation = rule.get_variation(hash_variation)
|
1074
|
+
variation = rule.experiment.get_variation(hash_variation)
|
1032
1075
|
unless variation.nil?
|
1033
|
-
|
1034
|
-
selected_rule = rule
|
1076
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(variation, rule)
|
1035
1077
|
break
|
1036
1078
|
end
|
1037
1079
|
# if visitor is targeted for targeted rule then break cycle -> return default
|
@@ -1041,39 +1083,44 @@ module Kameleoon
|
|
1041
1083
|
end
|
1042
1084
|
Logging::KameleoonLogger.debug(
|
1043
1085
|
"RETURN: KameleoonClient._calculate_variation_key_for_feature(visitor_code: '%s', feature_flag: %s) " \
|
1044
|
-
'-> (
|
1086
|
+
'-> (eval_exp: %s)', visitor_code, feature_flag, selected
|
1045
1087
|
)
|
1046
|
-
|
1088
|
+
selected
|
1047
1089
|
end
|
1048
1090
|
|
1049
|
-
def
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1091
|
+
def calculate_variation_key(eval_exp, default_value)
|
1092
|
+
Logging::KameleoonLogger.debug(
|
1093
|
+
"CALL: KameleoonClient.calculate_variation_key(eval_exp: %s, default_value: '%s') ", eval_exp, default_value
|
1094
|
+
)
|
1095
|
+
variation_key = eval_exp ? eval_exp.var_by_exp.variation_key : default_value
|
1096
|
+
Logging::KameleoonLogger.debug(
|
1097
|
+
"RETURN: KameleoonClient.calculate_variation_key(eval_exp: %s, default_value: '%s') -> (variation_key: '%s')",
|
1098
|
+
eval_exp, default_value, variation_key
|
1099
|
+
)
|
1100
|
+
variation_key
|
1054
1101
|
end
|
1055
1102
|
|
1056
|
-
def
|
1103
|
+
def create_external_variation(variation, eval_exp)
|
1057
1104
|
Logging::KameleoonLogger.debug(
|
1058
|
-
'CALL: KameleoonClient.
|
1059
|
-
internal_variation, variation_id, experiment_id
|
1105
|
+
'CALL: KameleoonClient.create_external_variation(variation: %s, eval_exp: %s)', variation, eval_exp
|
1060
1106
|
)
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1107
|
+
ext_variables = {}
|
1108
|
+
variation&.variables&.each do |variable|
|
1109
|
+
ext_variables[variable.key] = Types::Variable.new(
|
1064
1110
|
variable.key,
|
1065
1111
|
variable.type,
|
1066
1112
|
_parse_feature_variable(variable)
|
1067
1113
|
)
|
1068
1114
|
end
|
1069
|
-
|
1070
|
-
|
1115
|
+
ext_variables.freeze
|
1116
|
+
ext_variation = Types::Variation.new(
|
1117
|
+
variation&.key, eval_exp&.var_by_exp&.variation_id, eval_exp&.experiment&.id, ext_variables
|
1118
|
+
)
|
1071
1119
|
Logging::KameleoonLogger.debug(
|
1072
|
-
'RETURN: KameleoonClient.
|
1073
|
-
|
1074
|
-
internal_variation, variation_id, experiment_id, variation
|
1120
|
+
'RETURN: KameleoonClient.create_external_variation(variation: %s, eval_exp: %s) -> (ext_variation: %s)',
|
1121
|
+
variation, eval_exp, ext_variation
|
1075
1122
|
)
|
1076
|
-
|
1123
|
+
ext_variation
|
1077
1124
|
end
|
1078
1125
|
|
1079
1126
|
##
|
@@ -1096,5 +1143,31 @@ module Kameleoon
|
|
1096
1143
|
)
|
1097
1144
|
@visitor_manager.add_data(visitor_code, UniqueIdentifier.new(is_unique_identifier))
|
1098
1145
|
end
|
1146
|
+
|
1147
|
+
class EvaluatedExperiment
|
1148
|
+
attr_reader :var_by_exp, :experiment, :rule_type
|
1149
|
+
|
1150
|
+
def initialize(var_by_exp, experiment, rule_type)
|
1151
|
+
@var_by_exp = var_by_exp
|
1152
|
+
@experiment = experiment
|
1153
|
+
@rule_type = rule_type
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
def self.from_var_by_exp_rule(var_by_exp, rule)
|
1157
|
+
EvaluatedExperiment.new(var_by_exp, rule.experiment, rule.type)
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
def self.from_forced_variation(forced_variation)
|
1161
|
+
if forced_variation.var_by_exp.nil? || forced_variation.rule.nil?
|
1162
|
+
nil
|
1163
|
+
else
|
1164
|
+
EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, forced_variation.rule)
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def self.from_forced_experiment_variation(forced_variation)
|
1169
|
+
EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, forced_variation.rule)
|
1170
|
+
end
|
1171
|
+
end
|
1099
1172
|
end
|
1100
1173
|
end
|
@@ -163,12 +163,12 @@ module Kameleoon
|
|
163
163
|
end
|
164
164
|
return DataManager::ForcedFeatureVariation.new(feature_key, nil, nil, true) if experiment_id.zero?
|
165
165
|
|
166
|
-
rule = feature_flag.rules.find { |r| r.
|
166
|
+
rule = feature_flag.rules.find { |r| r.experiment.id == experiment_id }
|
167
167
|
unless rule
|
168
168
|
Logging::KameleoonLogger.error('Simulated experiment %d is not found', experiment_id)
|
169
169
|
return nil
|
170
170
|
end
|
171
|
-
var_by_exp = rule.
|
171
|
+
var_by_exp = rule.experiment.variations_by_exposition.find { |v| v.variation_id == variation_id }
|
172
172
|
unless var_by_exp
|
173
173
|
Logging::KameleoonLogger.error('Simulated variation %d is not found', variation_id)
|
174
174
|
return nil
|
@@ -25,11 +25,10 @@ module Kameleoon
|
|
25
25
|
end
|
26
26
|
|
27
27
|
private def check_rule(data, rule)
|
28
|
-
return false
|
29
|
-
|
28
|
+
return false unless rule.is_a?(Kameleoon::Configuration::Rule)
|
30
29
|
return false if !@condition_rule_id.nil? && (@condition_rule_id != rule.id)
|
31
30
|
|
32
|
-
variation = data.variations_storage.get(rule.
|
31
|
+
variation = data.variations_storage.get(rule.experiment.id)
|
33
32
|
return false if variation.nil?
|
34
33
|
|
35
34
|
return true if @condition_variation_key.nil?
|
@@ -14,13 +14,13 @@ module Kameleoon
|
|
14
14
|
|
15
15
|
def check_targeting(visitor_code, campaign_id, exp_ff_rule)
|
16
16
|
Logging::KameleoonLogger.debug(
|
17
|
-
"CALL: TargetingManager.check_targeting(
|
17
|
+
"CALL: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s)",
|
18
18
|
visitor_code, campaign_id, exp_ff_rule
|
19
19
|
)
|
20
20
|
segment = exp_ff_rule.targeting_segment
|
21
21
|
if segment.nil?
|
22
22
|
Logging::KameleoonLogger.debug(
|
23
|
-
"RETURN: TargetingManager.check_targeting(
|
23
|
+
"RETURN: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s) -> " \
|
24
24
|
'(targeting: true)', visitor_code, campaign_id, exp_ff_rule
|
25
25
|
)
|
26
26
|
return true
|
@@ -29,7 +29,7 @@ module Kameleoon
|
|
29
29
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
30
30
|
targeting = segment.check_tree(->(type) { get_condition_data(type, visitor, visitor_code, campaign_id) })
|
31
31
|
Logging::KameleoonLogger.debug(
|
32
|
-
"RETURN: TargetingManager.check_targeting(
|
32
|
+
"RETURN: TargetingManager.check_targeting(visitor_code: '%s', campaign_id: %s, exp_ff_rule: %s) -> " \
|
33
33
|
'(targeting: %s)', visitor_code, campaign_id, exp_ff_rule, targeting
|
34
34
|
)
|
35
35
|
targeting
|
data/lib/kameleoon/utils.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'fiber'
|
4
3
|
require 'bigdecimal'
|
4
|
+
require 'digest'
|
5
|
+
require 'fiber'
|
5
6
|
require 'kameleoon/utils'
|
6
7
|
require 'kameleoon/exceptions'
|
7
8
|
require 'kameleoon/logging/kameleoon_logger'
|
@@ -32,23 +33,19 @@ module Kameleoon
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
module
|
36
|
-
def self.obtain(visitor_code,
|
37
|
-
|
36
|
+
module Hasher
|
37
|
+
def self.obtain(visitor_code, container_id, suffix = nil)
|
38
|
+
calculate("#{visitor_code}#{container_id}#{suffix}")
|
38
39
|
end
|
39
40
|
|
40
|
-
def self.
|
41
|
-
|
41
|
+
def self.obtain_hash_for_me_group(visitor_code, me_group_name)
|
42
|
+
calculate("#{visitor_code}#{me_group_name}")
|
42
43
|
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
identifier += container_id.to_s
|
49
|
-
identifier += suffix.to_s
|
50
|
-
identifier += respool_times.sort.to_h.values.join.to_s if !respool_times.nil? && !respool_times.empty?
|
51
|
-
(Digest::SHA256.hexdigest(identifier.encode('UTF-8')).to_i(16) / (BigDecimal('2')**BigDecimal('256'))).round(16)
|
45
|
+
def self.calculate(string_to_hash)
|
46
|
+
parsed_value = Digest::SHA256.hexdigest(string_to_hash.encode('UTF-8')).to_i(16)
|
47
|
+
max_value = BigDecimal('2')**BigDecimal('256')
|
48
|
+
(parsed_value / max_value).round(16)
|
52
49
|
end
|
53
50
|
end
|
54
51
|
|
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.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -78,7 +78,9 @@ files:
|
|
78
78
|
- lib/kameleoon/client_readiness.rb
|
79
79
|
- lib/kameleoon/configuration/custom_data_info.rb
|
80
80
|
- lib/kameleoon/configuration/data_file.rb
|
81
|
+
- lib/kameleoon/configuration/experiment.rb
|
81
82
|
- lib/kameleoon/configuration/feature_flag.rb
|
83
|
+
- lib/kameleoon/configuration/me_group.rb
|
82
84
|
- lib/kameleoon/configuration/rule.rb
|
83
85
|
- lib/kameleoon/configuration/settings.rb
|
84
86
|
- lib/kameleoon/configuration/variable.rb
|