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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e0001c490d29beefab2f33c416affe19f6ccbc05d83e0aaad70be85070986e3
4
- data.tar.gz: 6959eb4d8f421b9e14e62f3c490d5e1c6f038a25d3e5cb785c1832d7908c291d
3
+ metadata.gz: 8d0ea98aa7ca7ea1585f83f6bd231aa577a774124b4332d18ca4e2c2649bfbc2
4
+ data.tar.gz: ec43717ed1dd3a61828a9c062f10040a7e214e070f30f6deb2a76bec9d60fb76
5
5
  SHA512:
6
- metadata.gz: dfe9e75f3563287b6f187a38066a623145ba7ed916d12b9c7ec03dfe7173600ddd16d6a49ca304c4c46ec28794d5372bf29534f36ebaea18fa7eb8c2977dd981
7
- data.tar.gz: faf2abe7ed35a211fc210a3e72f90a2bf79a8affde8a35c96af2363fabb50091b1069aaea0074458bff1d56156d80e81cd8cb427e3c772b99dc81d318093f7a9
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, :has_any_targeted_delivery_rule, :feature_flag_by_id, :rule_by_segment_id,
12
- :rule_info_by_exp_id, :variation_by_id, :custom_data_info, :experiment_ids_with_js_css_variable
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.experiment_id || 0] = RuleInfo.new(feature_flag, rule)
89
- rule.variation_by_exposition.each do |variation|
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.experiment_id) if has_feature_flag_variable_js_css
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:#{@feature_key}, " \
21
- "environment_enabled:#{@environment_enabled}, " \
22
- "default_variation_key:#{@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 'variation_exposition'
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, :experiment_id, :variation_by_exposition, :respool_time, :segment_id,
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
- end
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.experiment_id || 0] = forced_variation
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, var_by_exp, rule = get_variation_info(visitor_code, feature_flag, track)
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 = make_external_variation(variation, var_by_exp&.variation_id, rule&.experiment_id)
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, var_by_exp, rule = get_variation_info(visitor_code, feature_flag, track)
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
- forced_variation = visitor&.get_forced_feature_variation(feature_key)
628
- if forced_variation
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
- forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
669
- if forced_variation
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
- var_by_exp = forced_variation.var_by_exp
923
- rule = forced_variation.rule
924
- else
925
- var_by_exp, rule = _calculate_variation_key_for_feature(visitor_code, feature_flag)
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
- unless forced_variation&.simulated
928
- save_variation(visitor_code, rule, var_by_exp, track: track)
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.get_variation_info(visitor_code: '%s', feature_flag: %s, track: %s)" \
933
- ' -> (variation_key: %s, variation_by_exposition: %s, rule: %s)',
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
- [variation_key, var_by_exp, rule]
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
- forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
949
- if forced_variation
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, rule, var_by_exp, track: true)
967
- experiment_id = rule&.experiment_id
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', rule: %s, var_by_exp: %s, track: %s)",
973
- visitor_code, rule, var_by_exp, track
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(experiment_id, variation_id, rule.type)
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', rule: %s, var_by_exp: %s, track: %s)",
981
- visitor_code, rule, var_by_exp, track
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
- selected_variation = nil
995
- selected_rule = nil
1045
+ selected = nil
996
1046
  feature_flag.rules.each do |rule|
997
- forced_variation = visitor&.get_forced_experiment_variation(rule.experiment_id || 0)
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
- selected_variation = forced_variation.var_by_exp
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.experiment_id, 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
- selected_variation = forced_variation.var_by_exp
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::HashDouble.obtain_rule(code_for_hash, rule.id, rule.respool_time)
1017
- Logging::KameleoonLogger.debug("Calculated hash_rule: %s for visitor_code: '%s'", hash_rule, code_for_hash)
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
- selected_variation = rule.first_variation
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::HashDouble.obtain_rule(code_for_hash, rule.experiment_id, rule.respool_time)
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
- selected_variation = variation
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
- '-> (variation: %s, rule: %s)', visitor_code, feature_flag, selected_variation, selected_rule
1086
+ '-> (eval_exp: %s)', visitor_code, feature_flag, selected
1045
1087
  )
1046
- [selected_variation, selected_rule]
1088
+ selected
1047
1089
  end
1048
1090
 
1049
- def _get_variation_key(var_by_exp, rule, feature_flag)
1050
- return var_by_exp.variation_key unless var_by_exp.nil?
1051
- return Kameleoon::Configuration::VariationType::VARIATION_OFF if !rule.nil? && rule.experimentation_type?
1052
-
1053
- feature_flag.default_variation_key
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 make_external_variation(internal_variation, variation_id, experiment_id)
1103
+ def create_external_variation(variation, eval_exp)
1057
1104
  Logging::KameleoonLogger.debug(
1058
- 'CALL: KameleoonClient.make_external_variation(internal_variation: %s, variation_id: %s, experiment_id: %s)',
1059
- internal_variation, variation_id, experiment_id
1105
+ 'CALL: KameleoonClient.create_external_variation(variation: %s, eval_exp: %s)', variation, eval_exp
1060
1106
  )
1061
- variables = {}
1062
- internal_variation&.variables&.each do |variable|
1063
- variables[variable.key] = Types::Variable.new(
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
- variables.freeze
1070
- variation = Types::Variation.new(internal_variation&.key, variation_id, experiment_id, variables)
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.make_external_variation(internal_variation: %s, variation_id: %s, experiment_id: %s)' \
1073
- ' -> (variation: %s)',
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
- variation
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.experiment_id == experiment_id }
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.variation_by_exposition.find { |v| v.variation_id == variation_id }
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 if !rule.is_a?(Kameleoon::Configuration::Rule) || rule.experiment_id.nil?
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.experiment_id)
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(visitorCode: '%s', campaignId: %s, exp_ff_rule: %s)",
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(visitorCode: '%s', campaignId: %s, exp_ff_rule: %s) -> " \
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(visitorCode: '%s', campaignId: %s, exp_ff_rule: %s) -> " \
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
@@ -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 HashDouble
36
- def self.obtain(visitor_code, respool_times = {}, container_id = '')
37
- obtain_helper(visitor_code, respool_times, container_id, '')
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.obtain_rule(visitor_code, container_id = '', suffix = '')
41
- obtain_helper(visitor_code, {}, container_id, suffix)
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
- private
45
-
46
- def self.obtain_helper(visitor_code, respool_times, container_id, suffix)
47
- identifier = visitor_code.to_s
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
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kameleoon
4
- SDK_VERSION = '3.7.0'
4
+ SDK_VERSION = '3.9.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.7.0
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: 2024-12-16 00:00:00.000000000 Z
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