kameleoon-client-ruby 3.6.1 → 3.8.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 +19 -5
- data/lib/kameleoon/configuration/experiment.rb +46 -0
- data/lib/kameleoon/configuration/rule.rb +3 -19
- data/lib/kameleoon/data/data.rb +2 -0
- data/lib/kameleoon/data/manager/forced_experiment_variation.rb +25 -0
- data/lib/kameleoon/data/manager/forced_feature_variation.rb +28 -0
- data/lib/kameleoon/data/manager/forced_variation.rb +19 -0
- data/lib/kameleoon/data/manager/visitor.rb +99 -23
- data/lib/kameleoon/exceptions.rb +11 -4
- data/lib/kameleoon/kameleoon_client.rb +212 -76
- data/lib/kameleoon/network/cookie/cookie_manager.rb +123 -48
- data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +2 -3
- data/lib/kameleoon/targeting/targeting_manager.rb +3 -3
- data/lib/kameleoon/types/variation.rb +3 -1
- data/lib/kameleoon/utils.rb +9 -16
- data/lib/kameleoon/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a64d8edfee07c22561b166de49f9185d89a6fbdec9b49f6fd65860125bd54eab
|
4
|
+
data.tar.gz: ee7ccf78d0d11dec198b930e77e496c39a220b87471d724015c78114b85368f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccfba38b35f50d2b1dda0ef2bf31406f74fd92663276f4297ae867bc7b88b8606b345a8a4234f24ef71c71fdc3555cc9852d7324c1a6a668ed8b6cbc98cfeedb
|
7
|
+
data.tar.gz: 38543c67b7c8949ccba5c8e0f08eae86d2755a737bc25a5dc82e94dd92011c7678cb4b32647ffae0edb606913bff537455f2f42295fe9f421554726a57675808
|
@@ -1,15 +1,17 @@
|
|
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/settings'
|
6
7
|
require 'kameleoon/logging/kameleoon_logger'
|
7
8
|
|
8
9
|
module Kameleoon
|
9
10
|
module Configuration
|
10
11
|
class DataFile
|
11
12
|
attr_reader :settings, :feature_flags, :has_any_targeted_delivery_rule, :feature_flag_by_id, :rule_by_segment_id,
|
12
|
-
:variation_by_id, :custom_data_info, :experiment_ids_with_js_css_variable
|
13
|
+
:rule_info_by_exp_id, :variation_by_id, :custom_data_info, :experiment_ids_with_js_css_variable,
|
14
|
+
:holdout
|
13
15
|
|
14
16
|
def to_s
|
15
17
|
'DataFile{' \
|
@@ -64,6 +66,7 @@ module Kameleoon
|
|
64
66
|
end
|
65
67
|
@has_any_targeted_delivery_rule = any_targeted_delivery_rule?
|
66
68
|
@custom_data_info = CustomDataInfo.new(configuration['customData'])
|
69
|
+
@holdout = Experiment.from_json(configuration['holdout']) if configuration.include?('holdout')
|
67
70
|
Logging::KameleoonLogger.debug('RETURN: DataFile.init(configuration: %s)', configuration)
|
68
71
|
end
|
69
72
|
|
@@ -74,6 +77,7 @@ module Kameleoon
|
|
74
77
|
def collect_indices
|
75
78
|
@feature_flag_by_id = {}
|
76
79
|
@rule_by_segment_id = {}
|
80
|
+
@rule_info_by_exp_id = {}
|
77
81
|
@variation_by_id = {}
|
78
82
|
@experiment_ids_with_js_css_variable = Set.new
|
79
83
|
|
@@ -84,10 +88,11 @@ module Kameleoon
|
|
84
88
|
has_feature_flag_variable_js_css = feature_flag_variable_js_css?(feature_flag)
|
85
89
|
feature_flag.rules.each do |rule|
|
86
90
|
@rule_by_segment_id[rule.segment_id] = rule
|
87
|
-
rule.
|
91
|
+
@rule_info_by_exp_id[rule.experiment.id] = RuleInfo.new(feature_flag, rule)
|
92
|
+
rule.experiment.variations_by_exposition.each do |variation|
|
88
93
|
@variation_by_id[variation.variation_id] = variation
|
89
94
|
end
|
90
|
-
@experiment_ids_with_js_css_variable.add(rule.
|
95
|
+
@experiment_ids_with_js_css_variable.add(rule.experiment.id) if has_feature_flag_variable_js_css
|
91
96
|
end
|
92
97
|
end
|
93
98
|
@feature_flag_by_id.freeze
|
@@ -102,5 +107,14 @@ module Kameleoon
|
|
102
107
|
end
|
103
108
|
end
|
104
109
|
end
|
110
|
+
|
111
|
+
class RuleInfo
|
112
|
+
attr_reader :feature_flag, :rule
|
113
|
+
|
114
|
+
def initialize(feature_flag, rule)
|
115
|
+
@feature_flag = feature_flag
|
116
|
+
@rule = rule
|
117
|
+
end
|
118
|
+
end
|
105
119
|
end
|
106
120
|
end
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'experiment'
|
4
4
|
require 'kameleoon/targeting/models'
|
5
5
|
|
6
6
|
module Kameleoon
|
@@ -29,7 +29,7 @@ module Kameleoon
|
|
29
29
|
|
30
30
|
# Rule is a class for new rules of feature flags
|
31
31
|
class Rule
|
32
|
-
attr_reader :id, :order, :type, :exposition, :
|
32
|
+
attr_reader :id, :order, :type, :exposition, :experiment, :respool_time, :segment_id
|
33
33
|
attr_accessor :targeting_segment
|
34
34
|
|
35
35
|
def self.create_from_array(array)
|
@@ -45,26 +45,10 @@ module Kameleoon
|
|
45
45
|
@order = hash['order']
|
46
46
|
@type = RuleType.from_literal(hash['type'])
|
47
47
|
@exposition = hash['exposition']
|
48
|
-
@experiment_id = hash['experimentId']
|
49
48
|
@respool_time = hash['respoolTime']
|
50
|
-
@variation_by_exposition = VariationByExposition.create_from_array(hash['variationByExposition'])
|
51
|
-
@variation_by_exposition.freeze
|
52
|
-
@first_variation = @variation_by_exposition.first
|
53
49
|
@targeting_segment = Kameleoon::Targeting::Segment.new((hash['segment'])) if hash['segment']
|
54
50
|
@segment_id = @targeting_segment != nil ? targeting_segment.id : -1
|
55
|
-
|
56
|
-
|
57
|
-
def get_variation(hash_double)
|
58
|
-
total = 0.0
|
59
|
-
variation_by_exposition.each do |var_by_exp|
|
60
|
-
total += var_by_exp.exposition
|
61
|
-
return var_by_exp if total >= hash_double
|
62
|
-
end
|
63
|
-
nil
|
64
|
-
end
|
65
|
-
|
66
|
-
def get_variation_id_by_key(key)
|
67
|
-
variation_by_exposition.select { |v| v.variation_key == key }.first&.variation_id
|
51
|
+
@experiment = Experiment.from_json(hash)
|
68
52
|
end
|
69
53
|
|
70
54
|
def experimentation_type?
|
data/lib/kameleoon/data/data.rb
CHANGED
@@ -14,6 +14,8 @@ module Kameleoon
|
|
14
14
|
DEVICE = 'DEVICE'
|
15
15
|
PAGE_VIEW = 'PAGE_VIEW'
|
16
16
|
ASSIGNED_VARIATION = 'ASSIGNED_VARIATION'
|
17
|
+
FORCED_EXPERIMENT_VARIATION = 'FORCED_EXPERIMENT_VARIATION'
|
18
|
+
FORCED_FEATURE_VARIATION = 'FORCED_FEATURE_VARIATION'
|
17
19
|
OPERATING_SYSTEM = 'OPERATING_SYSTEM'
|
18
20
|
GEOLOCATION = 'GEOLOCATION'
|
19
21
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/data/data'
|
4
|
+
require 'kameleoon/data/manager/forced_variation'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
module DataManager
|
8
|
+
class ForcedExperimentVariation < ForcedVariation
|
9
|
+
attr_reader :force_targeting
|
10
|
+
|
11
|
+
def initialize(rule, var_by_exp, force_targeting)
|
12
|
+
super(DataType::FORCED_EXPERIMENT_VARIATION, rule, var_by_exp)
|
13
|
+
@force_targeting = force_targeting
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
super(other) && other.is_a?(ForcedExperimentVariation) && (@force_targeting == other.force_targeting)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"ForcedExperimentVariation{rule:#{@rule},var_by_exp:#{@var_by_exp},force_targeting:#{@force_targeting}}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/data/data'
|
4
|
+
require 'kameleoon/data/manager/forced_variation'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
module DataManager
|
8
|
+
class ForcedFeatureVariation < ForcedVariation
|
9
|
+
attr_reader :feature_key, :simulated
|
10
|
+
|
11
|
+
def initialize(feature_key, rule, var_by_exp, simulated)
|
12
|
+
super(DataType::FORCED_FEATURE_VARIATION, rule, var_by_exp)
|
13
|
+
@feature_key = feature_key
|
14
|
+
@simulated = simulated
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super(other) && other.is_a?(ForcedFeatureVariation) && \
|
19
|
+
(@feature_key == other.feature_key) && (@simulated == other.simulated)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"ForcedFeatureVariation{feature_key:'#{@feature_key}',rule:#{@rule}," \
|
24
|
+
"var_by_exp:#{@var_by_exp},simulated:#{@simulated}}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module DataManager
|
5
|
+
class ForcedVariation
|
6
|
+
attr_reader :instance, :rule, :var_by_exp, :rule_type, :assignment_time
|
7
|
+
|
8
|
+
def initialize(data_type, rule, var_by_exp)
|
9
|
+
@instance = data_type
|
10
|
+
@rule = rule
|
11
|
+
@var_by_exp = var_by_exp
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
(@rule == other.rule) && (@var_by_exp == other.var_by_exp)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -10,9 +10,11 @@ require 'kameleoon/data/page_view'
|
|
10
10
|
require 'kameleoon/data/unique_identifier'
|
11
11
|
require 'kameleoon/data/user_agent'
|
12
12
|
require 'kameleoon/data/manager/assigned_variation'
|
13
|
-
require 'kameleoon/data/manager/page_view_visit'
|
14
|
-
require 'kameleoon/data/manager/data_map_storage'
|
15
13
|
require 'kameleoon/data/manager/data_array_storage'
|
14
|
+
require 'kameleoon/data/manager/data_map_storage'
|
15
|
+
require 'kameleoon/data/manager/forced_experiment_variation'
|
16
|
+
require 'kameleoon/data/manager/forced_feature_variation'
|
17
|
+
require 'kameleoon/data/manager/page_view_visit'
|
16
18
|
require 'kameleoon/logging/kameleoon_logger'
|
17
19
|
|
18
20
|
module Kameleoon
|
@@ -163,38 +165,86 @@ module Kameleoon
|
|
163
165
|
variations
|
164
166
|
end
|
165
167
|
|
168
|
+
def get_forced_feature_variation(feature_key)
|
169
|
+
Logging::KameleoonLogger.debug("CALL: Visitor.get_forced_feature_variation(feature_key: '%s')", feature_key)
|
170
|
+
variation = @data.get_from_map(@data.simulated_variations, feature_key)
|
171
|
+
Logging::KameleoonLogger.debug(
|
172
|
+
"RETURN: Visitor.get_forced_feature_variation(feature_key: '%s') -> (variation: %s)",
|
173
|
+
feature_key, variation
|
174
|
+
)
|
175
|
+
variation
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_forced_experiment_variation(experiment_id)
|
179
|
+
Logging::KameleoonLogger.debug(
|
180
|
+
'CALL: Visitor.get_forced_experiment_variation(experiment_id: %d)', experiment_id
|
181
|
+
)
|
182
|
+
variation = @data.get_from_map(@data.forced_variations, experiment_id)
|
183
|
+
Logging::KameleoonLogger.debug(
|
184
|
+
'RETURN: Visitor.get_forced_experiment_variation(experiment_id: %d) -> (variation: %s)',
|
185
|
+
experiment_id, variation
|
186
|
+
)
|
187
|
+
variation
|
188
|
+
end
|
189
|
+
|
190
|
+
def reset_forced_experiment_variation(experiment_id)
|
191
|
+
Logging::KameleoonLogger.debug(
|
192
|
+
'CALL: Visitor.reset_forced_experiment_variation(experiment_id: %d)', experiment_id
|
193
|
+
)
|
194
|
+
@data.remove_from_map(@data.forced_variations, experiment_id)
|
195
|
+
Logging::KameleoonLogger.debug(
|
196
|
+
'RETURN: Visitor.reset_forced_experiment_variation(experiment_id: %d)', experiment_id
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_simulated_variations(variations)
|
201
|
+
return if (@data.simulated_variations.nil? || @data.simulated_variations.empty?) && variations.empty?
|
202
|
+
|
203
|
+
Logging::KameleoonLogger.debug('CALL: Visitor.update_simulated_variations(variations: %s)', variations)
|
204
|
+
new_simulated_variations = {}
|
205
|
+
variations.each { |sv| new_simulated_variations[sv.feature_key] = sv }
|
206
|
+
@data.mutex.with_write_lock do
|
207
|
+
@data.simulated_variations = new_simulated_variations
|
208
|
+
end
|
209
|
+
Logging::KameleoonLogger.debug('RETURN: Visitor.update_simulated_variations(variations: %s)', variations)
|
210
|
+
end
|
211
|
+
|
166
212
|
def add_data(*args, overwrite: true)
|
167
213
|
Logging::KameleoonLogger.debug('CALL: Visitor.add_data(args: %s, overwrite: %s)', args, overwrite)
|
168
214
|
@data.mutex.with_write_lock do
|
169
215
|
args.each do |data|
|
170
216
|
case data
|
171
|
-
when
|
217
|
+
when UserAgent
|
172
218
|
@data.user_agent = data.value
|
173
|
-
when
|
219
|
+
when AssignedVariation
|
174
220
|
@data.add_variation(data, overwrite)
|
175
|
-
when
|
221
|
+
when ForcedFeatureVariation
|
222
|
+
@data.add_forced_feature_variation(data)
|
223
|
+
when ForcedExperimentVariation
|
224
|
+
@data.add_forced_experiment_variation(data)
|
225
|
+
when Device
|
176
226
|
@data.set_device(data, overwrite)
|
177
|
-
when
|
227
|
+
when Browser
|
178
228
|
@data.set_browser(data, overwrite)
|
179
|
-
when
|
229
|
+
when CustomData
|
180
230
|
@data.add_custom_data(data, overwrite)
|
181
|
-
when
|
231
|
+
when PageView
|
182
232
|
@data.add_page_view(data)
|
183
|
-
when
|
233
|
+
when PageViewVisit
|
184
234
|
@data.add_page_view_visit(data)
|
185
|
-
when
|
235
|
+
when Conversion
|
186
236
|
@data.add_conversion(data)
|
187
|
-
when
|
237
|
+
when Cookie
|
188
238
|
@data.cookie = data
|
189
|
-
when
|
239
|
+
when OperatingSystem
|
190
240
|
@data.set_operating_system(data, overwrite)
|
191
|
-
when
|
241
|
+
when Geolocation
|
192
242
|
@data.set_geolocation(data, overwrite)
|
193
|
-
when
|
243
|
+
when KcsHeat
|
194
244
|
@data.kcs_heat = data
|
195
|
-
when
|
245
|
+
when VisitorVisits
|
196
246
|
@data.visitor_visits = data
|
197
|
-
when
|
247
|
+
when UniqueIdentifier
|
198
248
|
@is_unique_identifier = data.value
|
199
249
|
else
|
200
250
|
Logging::KameleoonLogger.warning("Data has unsupported type '%s'", data.class)
|
@@ -218,7 +268,7 @@ module Kameleoon
|
|
218
268
|
class VisitorData
|
219
269
|
attr_reader :mutex, :device, :browser, :geolocation, :operating_system
|
220
270
|
attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :visitor_visits,
|
221
|
-
:mapping_identifier
|
271
|
+
:mapping_identifier, :forced_variations, :simulated_variations
|
222
272
|
|
223
273
|
def initialize
|
224
274
|
Logging::KameleoonLogger.debug('CALL: VisitorData.new')
|
@@ -227,6 +277,22 @@ module Kameleoon
|
|
227
277
|
Logging::KameleoonLogger.debug('RETURN: VisitorData.new')
|
228
278
|
end
|
229
279
|
|
280
|
+
def get_from_map(map, key)
|
281
|
+
return nil if map.nil?
|
282
|
+
|
283
|
+
@mutex.with_read_lock do
|
284
|
+
return map[key]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def remove_from_map(map, key)
|
289
|
+
return false if map.nil?
|
290
|
+
|
291
|
+
@mutex.with_write_lock do
|
292
|
+
return map.delete(key).nil?
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
230
296
|
def enumerate_sendable_data(&blk)
|
231
297
|
blk.call(@device) unless @device.nil?
|
232
298
|
blk.call(@browser) unless @browser.nil?
|
@@ -278,7 +344,7 @@ module Kameleoon
|
|
278
344
|
end
|
279
345
|
|
280
346
|
def add_variation(variation, overwrite)
|
281
|
-
@variations
|
347
|
+
@variations ||= {}
|
282
348
|
if overwrite || !@variations.include?(variation.experiment_id)
|
283
349
|
@variations[variation.experiment_id] = variation
|
284
350
|
end
|
@@ -289,7 +355,7 @@ module Kameleoon
|
|
289
355
|
end
|
290
356
|
|
291
357
|
def add_custom_data(custom_data, overwrite)
|
292
|
-
@custom_data_map
|
358
|
+
@custom_data_map ||= {}
|
293
359
|
if overwrite || !@custom_data_map.include?(custom_data.id)
|
294
360
|
@custom_data_map[custom_data.id] = custom_data
|
295
361
|
end
|
@@ -298,10 +364,10 @@ module Kameleoon
|
|
298
364
|
def add_page_view(page_view)
|
299
365
|
return if page_view.url.nil? || page_view.url.empty?
|
300
366
|
|
301
|
-
@page_view_visits
|
367
|
+
@page_view_visits ||= {}
|
302
368
|
visit = @page_view_visits[page_view.url]
|
303
369
|
if visit.nil?
|
304
|
-
visit =
|
370
|
+
visit = PageViewVisit.new(page_view)
|
305
371
|
else
|
306
372
|
visit.overwrite(page_view)
|
307
373
|
end
|
@@ -309,7 +375,7 @@ module Kameleoon
|
|
309
375
|
end
|
310
376
|
|
311
377
|
def add_page_view_visit(page_view_visit)
|
312
|
-
@page_view_visits
|
378
|
+
@page_view_visits ||= {}
|
313
379
|
visit = @page_view_visits[page_view_visit.page_view.url]
|
314
380
|
if visit.nil?
|
315
381
|
visit = page_view_visit
|
@@ -320,7 +386,7 @@ module Kameleoon
|
|
320
386
|
end
|
321
387
|
|
322
388
|
def add_conversion(conversion)
|
323
|
-
@conversions
|
389
|
+
@conversions ||= []
|
324
390
|
@conversions.push(conversion)
|
325
391
|
end
|
326
392
|
|
@@ -331,6 +397,16 @@ module Kameleoon
|
|
331
397
|
def set_operating_system(operating_system, overwrite)
|
332
398
|
@operating_system = operating_system if overwrite || @operating_system.nil?
|
333
399
|
end
|
400
|
+
|
401
|
+
def add_forced_feature_variation(forced_variation)
|
402
|
+
@simulated_variations ||= {}
|
403
|
+
@simulated_variations[forced_variation.feature_key] = forced_variation
|
404
|
+
end
|
405
|
+
|
406
|
+
def add_forced_experiment_variation(forced_variation)
|
407
|
+
@forced_variations ||= {}
|
408
|
+
@forced_variations[forced_variation.rule.experiment.id] = forced_variation
|
409
|
+
end
|
334
410
|
end
|
335
411
|
end
|
336
412
|
end
|
data/lib/kameleoon/exceptions.rb
CHANGED
@@ -30,10 +30,10 @@ module Kameleoon
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
# Feature
|
34
|
-
class
|
35
|
-
def initialize(
|
36
|
-
super("
|
33
|
+
# Feature Experiment Not Found
|
34
|
+
class FeatureExperimentNotFound < FeatureError
|
35
|
+
def initialize(id = '')
|
36
|
+
super("Experiment #{id}")
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -44,6 +44,13 @@ module Kameleoon
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
# Feature Variable Not Found
|
48
|
+
class FeatureVariableNotFound < FeatureError
|
49
|
+
def initialize(key = '')
|
50
|
+
super("Feature variable #{key}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
47
54
|
# Feature Environment Disabled
|
48
55
|
class FeatureEnvironmentDisabled < FeatureError
|
49
56
|
def initialize(feature_key, environment = nil)
|
@@ -7,6 +7,7 @@ require 'kameleoon/configuration/data_file'
|
|
7
7
|
require 'kameleoon/data/custom_data'
|
8
8
|
require 'kameleoon/data/user_agent'
|
9
9
|
require 'kameleoon/data/manager/assigned_variation'
|
10
|
+
require 'kameleoon/data/manager/forced_experiment_variation'
|
10
11
|
require 'kameleoon/data/manager/visitor_manager'
|
11
12
|
require 'kameleoon/exceptions'
|
12
13
|
require 'kameleoon/hybrid/manager'
|
@@ -75,7 +76,7 @@ module Kameleoon
|
|
75
76
|
@remote_data_manager = Managers::RemoteData::RemoteDataManager.new(
|
76
77
|
@data_manager, @network_manager, @visitor_manager
|
77
78
|
)
|
78
|
-
@cookie_manager = Network::Cookie::CookieManager.new(@data_manager, config.top_level_domain)
|
79
|
+
@cookie_manager = Network::Cookie::CookieManager.new(@data_manager, @visitor_manager, config.top_level_domain)
|
79
80
|
@readiness = ClientReadiness.new
|
80
81
|
@targeting_manager = Targeting::TargetingManager.new(@data_manager, @visitor_manager)
|
81
82
|
|
@@ -320,9 +321,9 @@ module Kameleoon
|
|
320
321
|
)
|
321
322
|
Utils::VisitorCode.validate(visitor_code)
|
322
323
|
feature_flag = @data_manager.data_file.get_feature_flag(feature_key)
|
323
|
-
variation_key,
|
324
|
+
variation_key, eval_exp = get_variation_info(visitor_code, feature_flag, track)
|
324
325
|
variation = feature_flag.get_variation_by_key(variation_key)
|
325
|
-
external_variation =
|
326
|
+
external_variation = create_external_variation(variation, eval_exp)
|
326
327
|
@tracking_manager.add_visitor_code(visitor_code) if track
|
327
328
|
Logging::KameleoonLogger.info(
|
328
329
|
"RETURN: KameleoonClient.get_variation(visitor_code: '%s', feature_key: '%s', track: %s)" \
|
@@ -357,12 +358,11 @@ module Kameleoon
|
|
357
358
|
@data_manager.data_file.feature_flags.each_value do |feature_flag|
|
358
359
|
next unless feature_flag.environment_enabled
|
359
360
|
|
360
|
-
variation_key,
|
361
|
+
variation_key, eval_exp = get_variation_info(visitor_code, feature_flag, track)
|
361
362
|
next if only_active && (variation_key == Configuration::VariationType::VARIATION_OFF)
|
362
363
|
|
363
364
|
variation = feature_flag.get_variation_by_key(variation_key)
|
364
|
-
variations[feature_flag.feature_key] =
|
365
|
-
make_external_variation(variation, var_by_exp&.variation_id, rule&.experiment_id)
|
365
|
+
variations[feature_flag.feature_key] = create_external_variation(variation, eval_exp)
|
366
366
|
end
|
367
367
|
variations.freeze
|
368
368
|
@tracking_manager.add_visitor_code(visitor_code) if track
|
@@ -611,17 +611,20 @@ module Kameleoon
|
|
611
611
|
#
|
612
612
|
# DEPRECATED. Please use `get_active_features` instead.
|
613
613
|
def get_active_feature_list_for_visitor(visitor_code)
|
614
|
-
Logging::KameleoonLogger.info(
|
615
|
-
|
616
|
-
|
617
|
-
|
614
|
+
Logging::KameleoonLogger.info(
|
615
|
+
'[DEPRECATION] `get_active_feature_list_for_visitor` is deprecated. Please use `get_active_features` instead.'
|
616
|
+
)
|
617
|
+
Logging::KameleoonLogger.info(
|
618
|
+
"CALL: KameleoonClient.get_active_feature_list_for_visitor(visitor_code: '%s')", visitor_code
|
619
|
+
)
|
618
620
|
Utils::VisitorCode.validate(visitor_code)
|
621
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
619
622
|
list_keys = []
|
620
623
|
@data_manager.data_file.feature_flags.each do |feature_key, feature_flag|
|
621
624
|
next unless feature_flag.environment_enabled
|
622
625
|
|
623
|
-
|
624
|
-
variation_key =
|
626
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, false, false)
|
627
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
625
628
|
list_keys.push(feature_key) if variation_key != Kameleoon::Configuration::VariationType::VARIATION_OFF
|
626
629
|
end
|
627
630
|
Logging::KameleoonLogger.info(
|
@@ -649,19 +652,19 @@ module Kameleoon
|
|
649
652
|
)
|
650
653
|
Logging::KameleoonLogger.info("CALL: KameleoonClient.get_active_features(visitor_code: '%s')", visitor_code)
|
651
654
|
Utils::VisitorCode.validate(visitor_code)
|
655
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
652
656
|
map_active_features = {}
|
653
657
|
|
654
658
|
@data_manager.data_file.feature_flags.each_value do |feature_flag|
|
655
659
|
next unless feature_flag.environment_enabled
|
656
660
|
|
657
|
-
|
658
|
-
variation_key =
|
661
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, false, false)
|
662
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
659
663
|
|
660
664
|
next if variation_key == Configuration::VariationType::VARIATION_OFF
|
661
665
|
|
662
666
|
variation = feature_flag.get_variation_by_key(variation_key)
|
663
|
-
map_active_features[feature_flag.feature_key] =
|
664
|
-
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)
|
665
668
|
end
|
666
669
|
|
667
670
|
map_active_features.freeze
|
@@ -701,6 +704,52 @@ module Kameleoon
|
|
701
704
|
engine_tracking_code
|
702
705
|
end
|
703
706
|
|
707
|
+
##
|
708
|
+
# Sets or resets a forced variation for a visitor in a specific experiment,
|
709
|
+
# so the experiment will be evaluated to the variation for the visitor.
|
710
|
+
#
|
711
|
+
# In order to reset the forced variation set the `variation_key` parameter to `nil`.
|
712
|
+
# If the forced variation you want to reset does not exist, the method will have no effect.
|
713
|
+
#
|
714
|
+
# @param [String] visitor_code The unique visitor code identifying the visitor.
|
715
|
+
# @param [Integer] experiment_id The identifier of the experiment you want to set/reset the forced variation for.
|
716
|
+
# @param [String | NilClass] variation_key The identifier of the variation you want the experiment to be evaluated
|
717
|
+
# to. Set to `nil` to reset the forced variation.
|
718
|
+
# @param [Bool] force_targeting If `true`, the visitor will be targeted to the experiment regardless its
|
719
|
+
# conditions. Otherwise, the normal targeting logic will be preserved. Optional (defaults to `true`).
|
720
|
+
#
|
721
|
+
# @raise [Kameleoon::Exception::VisitorCodeInvalid] The provided **visitor code** is invalid.
|
722
|
+
# @raise [Kameleoon::Exception::FeatureExperimentNotFound] The provided **experiment id** does not exist in
|
723
|
+
# the feature flag.
|
724
|
+
# @raise [Kameleoon::Exception::FeatureVariationNotFound] The provided **variation key** does not belong to
|
725
|
+
# the experiment.
|
726
|
+
def set_forced_variation(visitor_code, experiment_id, variation_key, force_targeting: true)
|
727
|
+
Logging::KameleoonLogger.info(
|
728
|
+
"CALL: KameleoonClient.set_forced_variation(visitor_code: '%s', experiment_id: %d, variation_key: %s, " \
|
729
|
+
'force_targeting: %s)',
|
730
|
+
visitor_code, experiment_id, variation_key.nil? ? 'nil' : "'#{variation_key}'", force_targeting
|
731
|
+
)
|
732
|
+
Utils::VisitorCode.validate(visitor_code)
|
733
|
+
if variation_key.nil?
|
734
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
735
|
+
visitor&.reset_forced_experiment_variation(experiment_id)
|
736
|
+
else
|
737
|
+
rule_info = @data_manager.data_file.rule_info_by_exp_id[experiment_id]
|
738
|
+
if rule_info.nil?
|
739
|
+
raise Exception::FeatureExperimentNotFound.new(experiment_id), "Experiment #{experiment_id} is not found"
|
740
|
+
end
|
741
|
+
|
742
|
+
var_by_exp = rule_info.rule.experiment.get_variation_by_key(variation_key)
|
743
|
+
forced_variation = DataManager::ForcedExperimentVariation.new(rule_info.rule, var_by_exp, force_targeting)
|
744
|
+
@visitor_manager.add_data(visitor_code, forced_variation)
|
745
|
+
end
|
746
|
+
Logging::KameleoonLogger.info(
|
747
|
+
"RETURN: KameleoonClient.set_forced_variation(visitor_code: '%s', experiment_id: %d, variation_key: %s, " \
|
748
|
+
'force_targeting: %s)',
|
749
|
+
visitor_code, experiment_id, variation_key.nil? ? 'nil' : "'#{variation_key}'", force_targeting
|
750
|
+
)
|
751
|
+
end
|
752
|
+
|
704
753
|
private
|
705
754
|
|
706
755
|
HYBRID_EXPIRATION_TIME = 5
|
@@ -853,15 +902,65 @@ module Kameleoon
|
|
853
902
|
"CALL: KameleoonClient.get_variation_info(visitor_code: '%s', feature_flag: %s, track: %s)",
|
854
903
|
visitor_code, feature_flag, track
|
855
904
|
)
|
856
|
-
|
857
|
-
|
858
|
-
|
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)
|
859
908
|
Logging::KameleoonLogger.debug(
|
860
909
|
"RETURN: KameleoonClient.get_variation_info(visitor_code: '%s', feature_flag: %s, track: %s)" \
|
861
|
-
' -> (variation_key: %s,
|
862
|
-
visitor_code, feature_flag, track, variation_key,
|
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
|
922
|
+
forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
|
923
|
+
if forced_variation
|
924
|
+
eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
|
925
|
+
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save)
|
926
|
+
eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
927
|
+
end
|
928
|
+
save_variation(visitor_code, eval_exp, track: track) if save && !forced_variation&.simulated
|
929
|
+
Logging::KameleoonLogger.debug(
|
930
|
+
"RETURN: KameleoonClient.evaluate(visitor, visitor_code: '%s', feature_flag: %s, track: %s, save: %s)" \
|
931
|
+
' -> (eval_exp: %s)',
|
932
|
+
visitor_code, feature_flag, track, save, eval_exp
|
933
|
+
)
|
934
|
+
eval_exp
|
935
|
+
end
|
936
|
+
|
937
|
+
def visitor_not_in_holdout?(visitor, visitor_code, track, save)
|
938
|
+
in_holdout_variation_key = 'in-holdout'
|
939
|
+
Logging::KameleoonLogger.debug(
|
940
|
+
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)",
|
941
|
+
visitor_code, track, save
|
942
|
+
)
|
943
|
+
holdout = @data_manager.data_file.holdout
|
944
|
+
is_not_in_holdout = true
|
945
|
+
unless holdout.nil?
|
946
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
947
|
+
variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
|
948
|
+
Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
|
949
|
+
var_by_exp = holdout.get_variation(variation_hash)
|
950
|
+
unless var_by_exp.nil?
|
951
|
+
is_not_in_holdout = var_by_exp.variation_key != in_holdout_variation_key
|
952
|
+
if save
|
953
|
+
eval_exp = EvaluatedExperiment.new(var_by_exp, holdout, Configuration::RuleType::EXPERIMENTATION)
|
954
|
+
save_variation(visitor_code, eval_exp, track: track)
|
955
|
+
end
|
956
|
+
end
|
957
|
+
end
|
958
|
+
Logging::KameleoonLogger.debug(
|
959
|
+
"RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s)" \
|
960
|
+
' -> (is_not_in_holdout: %s)',
|
961
|
+
visitor_code, track, save, is_not_in_holdout
|
863
962
|
)
|
864
|
-
|
963
|
+
is_not_in_holdout
|
865
964
|
end
|
866
965
|
|
867
966
|
##
|
@@ -872,9 +971,9 @@ module Kameleoon
|
|
872
971
|
visitor_code, feature_key
|
873
972
|
)
|
874
973
|
feature_flag = @data_manager.data_file.get_feature_flag(feature_key)
|
875
|
-
|
876
|
-
|
877
|
-
|
974
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
975
|
+
eval_exp = evaluate(visitor, visitor_code, feature_flag, true, true)
|
976
|
+
variation_key = calculate_variation_key(eval_exp, feature_flag.default_variation_key)
|
878
977
|
@tracking_manager.add_visitor_code(visitor_code)
|
879
978
|
Logging::KameleoonLogger.debug(
|
880
979
|
"RETURN: KameleoonClient._get_feature_variation_key(visitor_code: '%s', feature_key: '%s')" \
|
@@ -884,25 +983,30 @@ module Kameleoon
|
|
884
983
|
[feature_flag, variation_key]
|
885
984
|
end
|
886
985
|
|
887
|
-
def save_variation(visitor_code,
|
888
|
-
|
889
|
-
variation_id = var_by_exp&.variation_id
|
890
|
-
return if experiment_id.nil? || variation_id.nil?
|
986
|
+
def save_variation(visitor_code, eval_exp, track: true)
|
987
|
+
return if eval_exp.nil? || eval_exp.experiment.id.zero? || eval_exp.var_by_exp.variation_id.nil?
|
891
988
|
|
892
989
|
Logging::KameleoonLogger.debug(
|
893
|
-
"CALL: KameleoonClient.save_variation(visitor_code: '%s',
|
894
|
-
visitor_code,
|
990
|
+
"CALL: KameleoonClient.save_variation(visitor_code: '%s', eval_exp: %s, track: %s)",
|
991
|
+
visitor_code, eval_exp, track
|
895
992
|
)
|
896
993
|
visitor = @visitor_manager.get_or_create_visitor(visitor_code)
|
897
|
-
as_variation = Kameleoon::DataManager::AssignedVariation.new(
|
994
|
+
as_variation = Kameleoon::DataManager::AssignedVariation.new(
|
995
|
+
eval_exp.experiment.id, eval_exp.var_by_exp.variation_id, eval_exp.rule_type
|
996
|
+
)
|
898
997
|
as_variation.mark_as_sent unless track
|
899
998
|
visitor.assign_variation(as_variation)
|
900
999
|
Logging::KameleoonLogger.debug(
|
901
|
-
"RETURN: KameleoonClient.save_variation(visitor_code: '%s',
|
902
|
-
visitor_code,
|
1000
|
+
"RETURN: KameleoonClient.save_variation(visitor_code: '%s', eval_exp: %s, track: %s)",
|
1001
|
+
visitor_code, eval_exp, track
|
903
1002
|
)
|
904
1003
|
end
|
905
1004
|
|
1005
|
+
def get_code_for_hash(visitor, visitor_code)
|
1006
|
+
# use mappingIdentifier instead of visitor_code if it was set up
|
1007
|
+
visitor&.mapping_identifier || visitor_code
|
1008
|
+
end
|
1009
|
+
|
906
1010
|
##
|
907
1011
|
# helper method for calculate variation key for feature flag
|
908
1012
|
def _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
@@ -910,41 +1014,42 @@ module Kameleoon
|
|
910
1014
|
"CALL: KameleoonClient._calculate_variation_key_for_feature(visitor_code: '%s', feature_flag: %s)",
|
911
1015
|
visitor_code, feature_flag
|
912
1016
|
)
|
1017
|
+
visitor = @visitor_manager.get_visitor(visitor_code)
|
1018
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
913
1019
|
# no rules -> return default_variation_key
|
1020
|
+
selected = nil
|
914
1021
|
feature_flag.rules.each do |rule|
|
1022
|
+
forced_variation = visitor&.get_forced_experiment_variation(rule.experiment.id)
|
1023
|
+
if forced_variation&.force_targeting
|
1024
|
+
# Forcing experiment variation in force-targeting mode
|
1025
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
|
1026
|
+
break
|
1027
|
+
end
|
915
1028
|
# check if visitor is targeted for rule, else next rule
|
916
|
-
next unless check_targeting(visitor_code, rule.
|
1029
|
+
next unless check_targeting(visitor_code, rule.experiment.id, rule)
|
917
1030
|
|
918
|
-
|
919
|
-
|
920
|
-
|
1031
|
+
unless forced_variation.nil?
|
1032
|
+
# Forcing experiment variation in targeting-only mode
|
1033
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, rule)
|
1034
|
+
break
|
1035
|
+
end
|
921
1036
|
# uses for rule exposition
|
922
|
-
hash_rule = Utils::
|
923
|
-
Logging::KameleoonLogger.debug("Calculated
|
1037
|
+
hash_rule = Utils::Hasher.obtain(code_for_hash, rule.id, rule.respool_time)
|
1038
|
+
Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
|
924
1039
|
# check main expostion for rule with hashRule
|
925
1040
|
if hash_rule <= rule.exposition
|
926
1041
|
if rule.targeted_delivery_type?
|
927
|
-
|
928
|
-
|
929
|
-
'-> (variation: %s, rule: %s)',
|
930
|
-
visitor_code, feature_flag, rule.first_variation, rule
|
931
|
-
)
|
932
|
-
return [rule.first_variation, rule]
|
1042
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(rule.experiment.variations_by_exposition.first, rule)
|
1043
|
+
break
|
933
1044
|
end
|
934
|
-
|
935
1045
|
# uses for variation's expositions
|
936
|
-
hash_variation = Utils::
|
937
|
-
Logging::KameleoonLogger.debug(
|
938
|
-
"Calculated hash_variation: %s for visitor_code: '%s'", hash_variation, code_for_hash
|
939
|
-
)
|
1046
|
+
hash_variation = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
|
1047
|
+
Logging::KameleoonLogger.debug("Calculated variation hash %s for code '%s'", hash_variation, code_for_hash)
|
940
1048
|
# get variation key with new hashVariation
|
941
|
-
variation = rule.get_variation(hash_variation)
|
1049
|
+
variation = rule.experiment.get_variation(hash_variation)
|
942
1050
|
unless variation.nil?
|
943
|
-
|
944
|
-
|
945
|
-
'feature_flag: %s) -> (variation: %s, rule: %s)', visitor_code, feature_flag, variation, rule
|
946
|
-
)
|
947
|
-
return [variation, rule]
|
1051
|
+
selected = EvaluatedExperiment.from_var_by_exp_rule(variation, rule)
|
1052
|
+
break
|
948
1053
|
end
|
949
1054
|
# if visitor is targeted for targeted rule then break cycle -> return default
|
950
1055
|
elsif rule.targeted_delivery_type?
|
@@ -953,39 +1058,44 @@ module Kameleoon
|
|
953
1058
|
end
|
954
1059
|
Logging::KameleoonLogger.debug(
|
955
1060
|
"RETURN: KameleoonClient._calculate_variation_key_for_feature(visitor_code: '%s', feature_flag: %s) " \
|
956
|
-
'-> (
|
1061
|
+
'-> (eval_exp: %s)', visitor_code, feature_flag, selected
|
957
1062
|
)
|
958
|
-
|
1063
|
+
selected
|
959
1064
|
end
|
960
1065
|
|
961
|
-
def
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
1066
|
+
def calculate_variation_key(eval_exp, default_value)
|
1067
|
+
Logging::KameleoonLogger.debug(
|
1068
|
+
"CALL: KameleoonClient.calculate_variation_key(eval_exp: %s, default_value: '%s') ", eval_exp, default_value
|
1069
|
+
)
|
1070
|
+
variation_key = eval_exp ? eval_exp.var_by_exp.variation_key : default_value
|
1071
|
+
Logging::KameleoonLogger.debug(
|
1072
|
+
"RETURN: KameleoonClient.calculate_variation_key(eval_exp: %s, default_value: '%s') -> (variation_key: '%s')",
|
1073
|
+
eval_exp, default_value, variation_key
|
1074
|
+
)
|
1075
|
+
variation_key
|
966
1076
|
end
|
967
1077
|
|
968
|
-
def
|
1078
|
+
def create_external_variation(variation, eval_exp)
|
969
1079
|
Logging::KameleoonLogger.debug(
|
970
|
-
'CALL: KameleoonClient.
|
971
|
-
internal_variation, variation_id, experiment_id
|
1080
|
+
'CALL: KameleoonClient.create_external_variation(variation: %s, eval_exp: %s)', variation, eval_exp
|
972
1081
|
)
|
973
|
-
|
974
|
-
|
975
|
-
|
1082
|
+
ext_variables = {}
|
1083
|
+
variation&.variables&.each do |variable|
|
1084
|
+
ext_variables[variable.key] = Types::Variable.new(
|
976
1085
|
variable.key,
|
977
1086
|
variable.type,
|
978
1087
|
_parse_feature_variable(variable)
|
979
1088
|
)
|
980
1089
|
end
|
981
|
-
|
982
|
-
|
1090
|
+
ext_variables.freeze
|
1091
|
+
ext_variation = Types::Variation.new(
|
1092
|
+
variation&.key, eval_exp&.var_by_exp&.variation_id, eval_exp&.experiment&.id, ext_variables
|
1093
|
+
)
|
983
1094
|
Logging::KameleoonLogger.debug(
|
984
|
-
'RETURN: KameleoonClient.
|
985
|
-
|
986
|
-
internal_variation, variation_id, experiment_id, variation
|
1095
|
+
'RETURN: KameleoonClient.create_external_variation(variation: %s, eval_exp: %s) -> (ext_variation: %s)',
|
1096
|
+
variation, eval_exp, ext_variation
|
987
1097
|
)
|
988
|
-
|
1098
|
+
ext_variation
|
989
1099
|
end
|
990
1100
|
|
991
1101
|
##
|
@@ -1008,5 +1118,31 @@ module Kameleoon
|
|
1008
1118
|
)
|
1009
1119
|
@visitor_manager.add_data(visitor_code, UniqueIdentifier.new(is_unique_identifier))
|
1010
1120
|
end
|
1121
|
+
|
1122
|
+
class EvaluatedExperiment
|
1123
|
+
attr_reader :var_by_exp, :experiment, :rule_type
|
1124
|
+
|
1125
|
+
def initialize(var_by_exp, experiment, rule_type)
|
1126
|
+
@var_by_exp = var_by_exp
|
1127
|
+
@experiment = experiment
|
1128
|
+
@rule_type = rule_type
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
def self.from_var_by_exp_rule(var_by_exp, rule)
|
1132
|
+
EvaluatedExperiment.new(var_by_exp, rule.experiment, rule.type)
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
def self.from_forced_variation(forced_variation)
|
1136
|
+
if forced_variation.var_by_exp.nil? || forced_variation.rule.nil?
|
1137
|
+
nil
|
1138
|
+
else
|
1139
|
+
EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, forced_variation.rule)
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
def self.from_forced_experiment_variation(forced_variation)
|
1144
|
+
EvaluatedExperiment.from_var_by_exp_rule(forced_variation.var_by_exp, forced_variation.rule)
|
1145
|
+
end
|
1146
|
+
end
|
1011
1147
|
end
|
1012
1148
|
end
|
@@ -1,21 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'kameleoon/
|
3
|
+
require 'kameleoon/data/manager/forced_feature_variation'
|
4
4
|
require 'kameleoon/logging/kameleoon_logger'
|
5
|
+
require 'kameleoon/utils'
|
5
6
|
|
6
7
|
module Kameleoon
|
7
8
|
module Network
|
8
9
|
module Cookie
|
9
|
-
COOKIE_KEY_JS = '_js_'
|
10
10
|
VISITOR_CODE_COOKIE = 'kameleoonVisitorCode'
|
11
|
+
KAMELEOON_SIMULATION_FF_DATA = 'kameleoonSimulationFFData'
|
12
|
+
EXPERIMENT_ID_KEY = 'expId'
|
13
|
+
VARIATION_ID_KEY = 'varId'
|
11
14
|
COOKIE_TTL_SECONDS = 380 * 86_400 # 380 days in seconds
|
12
15
|
|
13
16
|
class CookieManager
|
14
|
-
def initialize(data_manager, top_level_domain)
|
15
|
-
Logging::KameleoonLogger.debug(
|
17
|
+
def initialize(data_manager, visitor_manager, top_level_domain)
|
18
|
+
Logging::KameleoonLogger.debug(
|
19
|
+
"CALL: CookieManager.new(data_manager, visitor_manager, top_level_domain: '%s')", top_level_domain
|
20
|
+
)
|
16
21
|
@data_manager = data_manager
|
22
|
+
@visitor_manager = visitor_manager
|
17
23
|
@top_level_domain = top_level_domain
|
18
|
-
Logging::KameleoonLogger.debug(
|
24
|
+
Logging::KameleoonLogger.debug(
|
25
|
+
"RETURN: CookieManager.new(data_manager, visitor_manager, top_level_domain: '%s')", top_level_domain
|
26
|
+
)
|
19
27
|
end
|
20
28
|
|
21
29
|
def get_or_add(cookies, default_visitor_code = nil)
|
@@ -25,35 +33,8 @@ module Kameleoon
|
|
25
33
|
"CALL: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s')",
|
26
34
|
cookies, default_visitor_code
|
27
35
|
)
|
28
|
-
|
29
|
-
|
30
|
-
unless visitor_code.nil?
|
31
|
-
Utils::VisitorCode.validate(visitor_code)
|
32
|
-
Logging::KameleoonLogger.debug("Read visitor code '%s' from cookies %s", visitor_code, cookies)
|
33
|
-
# Remove adding cookies when we will be sure that it doesn't break anything
|
34
|
-
add(visitor_code, cookies) unless @data_manager.visitor_code_managed?
|
35
|
-
Logging::KameleoonLogger.debug(
|
36
|
-
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
37
|
-
cookies, default_visitor_code, visitor_code
|
38
|
-
)
|
39
|
-
return visitor_code
|
40
|
-
end
|
41
|
-
|
42
|
-
if default_visitor_code.nil?
|
43
|
-
visitor_code = Utils::VisitorCode.generate
|
44
|
-
Logging::KameleoonLogger.debug("Generated new visitor code '%s'", visitor_code)
|
45
|
-
add(visitor_code, cookies) unless @data_manager.visitor_code_managed?
|
46
|
-
Logging::KameleoonLogger.debug(
|
47
|
-
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
48
|
-
cookies, default_visitor_code, visitor_code
|
49
|
-
)
|
50
|
-
return visitor_code
|
51
|
-
end
|
52
|
-
|
53
|
-
visitor_code = default_visitor_code
|
54
|
-
Utils::VisitorCode.validate(visitor_code)
|
55
|
-
Logging::KameleoonLogger.debug("Used default visitor code '{%s}'", default_visitor_code)
|
56
|
-
add(visitor_code, cookies)
|
36
|
+
visitor_code = get_or_add_visitor_code(cookies, default_visitor_code)
|
37
|
+
process_simulated_variations(cookies, visitor_code)
|
57
38
|
Logging::KameleoonLogger.debug(
|
58
39
|
"RETURN: CookieManager.get_or_add(cookies: %s, default_visitor_code: '%s') -> (visitor_code: '%s')",
|
59
40
|
cookies, default_visitor_code, visitor_code
|
@@ -78,9 +59,34 @@ module Kameleoon
|
|
78
59
|
|
79
60
|
private
|
80
61
|
|
62
|
+
def get_or_add_visitor_code(cookies, default_visitor_code)
|
63
|
+
visitor_code = get_value_from_cookies(cookies, VISITOR_CODE_COOKIE)
|
64
|
+
unless visitor_code.nil?
|
65
|
+
Utils::VisitorCode.validate(visitor_code)
|
66
|
+
Logging::KameleoonLogger.debug("Read visitor code '%s' from cookies %s", visitor_code, cookies)
|
67
|
+
# Remove adding cookies when we will be sure that it doesn't break anything
|
68
|
+
add(visitor_code, cookies) unless @data_manager.visitor_code_managed?
|
69
|
+
return visitor_code
|
70
|
+
end
|
71
|
+
|
72
|
+
if default_visitor_code.nil?
|
73
|
+
visitor_code = Utils::VisitorCode.generate
|
74
|
+
Logging::KameleoonLogger.debug("Generated new visitor code '%s'", visitor_code)
|
75
|
+
add(visitor_code, cookies) unless @data_manager.visitor_code_managed?
|
76
|
+
return visitor_code
|
77
|
+
end
|
78
|
+
|
79
|
+
visitor_code = default_visitor_code
|
80
|
+
Utils::VisitorCode.validate(visitor_code)
|
81
|
+
Logging::KameleoonLogger.debug("Used default visitor code '{%s}'", default_visitor_code)
|
82
|
+
add(visitor_code, cookies)
|
83
|
+
visitor_code
|
84
|
+
end
|
85
|
+
|
81
86
|
def add(visitor_code, cookies)
|
82
|
-
Logging::KameleoonLogger.debug(
|
83
|
-
|
87
|
+
Logging::KameleoonLogger.debug(
|
88
|
+
"CALL: CookieManager.add(visitor_code: '%s', cookies: %s)", visitor_code, cookies
|
89
|
+
)
|
84
90
|
cookie = {
|
85
91
|
value: visitor_code,
|
86
92
|
expires: Time.now + COOKIE_TTL_SECONDS,
|
@@ -88,8 +94,9 @@ module Kameleoon
|
|
88
94
|
domain: @top_level_domain
|
89
95
|
}
|
90
96
|
cookies[VISITOR_CODE_COOKIE] = cookie
|
91
|
-
Logging::KameleoonLogger.debug(
|
92
|
-
|
97
|
+
Logging::KameleoonLogger.debug(
|
98
|
+
"RETURN: CookieManager.add(visitor_code: '%s', cookies: %s)", visitor_code, cookies
|
99
|
+
)
|
93
100
|
cookies
|
94
101
|
end
|
95
102
|
|
@@ -100,22 +107,90 @@ module Kameleoon
|
|
100
107
|
cookies
|
101
108
|
end
|
102
109
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
110
|
+
def process_simulated_variations(cookies, visitor_code)
|
111
|
+
raw = get_value_from_cookies(cookies, KAMELEOON_SIMULATION_FF_DATA)
|
112
|
+
return if raw.nil?
|
113
|
+
|
114
|
+
variations = parse_simulated_variations(raw)
|
115
|
+
visitor = @visitor_manager.get_or_create_visitor(visitor_code)
|
116
|
+
visitor.update_simulated_variations(variations)
|
117
|
+
rescue StandardError => e
|
118
|
+
Logging::KameleoonLogger.error('Failed to process simulated variations cookie: %s', e)
|
119
|
+
end
|
120
|
+
|
121
|
+
def parse_simulated_variations(raw)
|
122
|
+
data_file = @data_manager.data_file
|
123
|
+
jobj = JSON.parse(raw)
|
124
|
+
return nil unless jobj.is_a?(Hash)
|
125
|
+
|
126
|
+
variations = []
|
127
|
+
jobj.each do |feature_key, value|
|
128
|
+
unless feature_key.is_a?(String) && value.is_a?(Hash)
|
129
|
+
log_malformed_simulated_variations_cookie(raw)
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
experiment_id = value[EXPERIMENT_ID_KEY]
|
134
|
+
if !experiment_id.is_a?(Integer) || experiment_id.negative?
|
135
|
+
log_malformed_simulated_variations_cookie(raw)
|
136
|
+
next
|
137
|
+
end
|
138
|
+
|
139
|
+
unless experiment_id.zero?
|
140
|
+
variation_id = value[VARIATION_ID_KEY]
|
141
|
+
if !variation_id.is_a?(Integer) || variation_id.negative?
|
142
|
+
log_malformed_simulated_variations_cookie(raw)
|
143
|
+
next
|
144
|
+
end
|
145
|
+
end
|
146
|
+
simulated_variation = simulated_variation_from_data_file(
|
147
|
+
data_file, feature_key, experiment_id, variation_id
|
148
|
+
)
|
149
|
+
variations.push(simulated_variation) unless simulated_variation.nil?
|
150
|
+
end
|
151
|
+
variations
|
152
|
+
end
|
153
|
+
|
154
|
+
def log_malformed_simulated_variations_cookie(raw)
|
155
|
+
Logging::KameleoonLogger.error('Malformed simulated variations cookie: %s', raw)
|
156
|
+
end
|
157
|
+
|
158
|
+
def simulated_variation_from_data_file(data_file, feature_key, experiment_id, variation_id)
|
159
|
+
feature_flag = data_file.feature_flags[feature_key]
|
160
|
+
unless feature_flag
|
161
|
+
Logging::KameleoonLogger.error("Simulated feature flag '%s' is not found", feature_key)
|
162
|
+
return nil
|
163
|
+
end
|
164
|
+
return DataManager::ForcedFeatureVariation.new(feature_key, nil, nil, true) if experiment_id.zero?
|
165
|
+
|
166
|
+
rule = feature_flag.rules.find { |r| r.experiment.id == experiment_id }
|
167
|
+
unless rule
|
168
|
+
Logging::KameleoonLogger.error('Simulated experiment %d is not found', experiment_id)
|
169
|
+
return nil
|
170
|
+
end
|
171
|
+
var_by_exp = rule.experiment.variations_by_exposition.find { |v| v.variation_id == variation_id }
|
172
|
+
unless var_by_exp
|
173
|
+
Logging::KameleoonLogger.error('Simulated variation %d is not found', variation_id)
|
174
|
+
return nil
|
175
|
+
end
|
176
|
+
DataManager::ForcedFeatureVariation.new(feature_key, rule, var_by_exp, true)
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_value_from_cookies(cookies, key)
|
180
|
+
Logging::KameleoonLogger.debug('CALL: CookieManager.get_value_from_cookies(cookies: %s)', cookies)
|
181
|
+
cookie = cookies[key]
|
106
182
|
case cookie
|
107
183
|
when String
|
108
|
-
|
184
|
+
value = cookie
|
109
185
|
when Hash
|
110
|
-
|
186
|
+
value = cookie[:value]
|
111
187
|
end
|
112
|
-
|
113
|
-
visitor_code = nil if visitor_code&.empty?
|
188
|
+
value = nil if value&.empty?
|
114
189
|
Logging::KameleoonLogger.debug(
|
115
|
-
"RETURN: CookieManager.
|
116
|
-
cookies,
|
190
|
+
"RETURN: CookieManager.get_value_from_cookies(cookies: %s) -> (value: '%s')",
|
191
|
+
cookies, value
|
117
192
|
)
|
118
|
-
|
193
|
+
value
|
119
194
|
end
|
120
195
|
end
|
121
196
|
end
|
@@ -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
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kameleoon/configuration/variation'
|
4
|
+
|
3
5
|
module Kameleoon
|
4
6
|
# Module which contains all internal data of SDK
|
5
7
|
module Types
|
@@ -19,7 +21,7 @@ module Kameleoon
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def active?
|
22
|
-
|
24
|
+
@key != Configuration::VariationType::VARIATION_OFF
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
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,15 @@ 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
|
-
|
42
|
-
|
43
|
-
|
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)
|
41
|
+
def self.calculate(string_to_hash)
|
42
|
+
parsed_value = Digest::SHA256.hexdigest(string_to_hash.encode('UTF-8')).to_i(16)
|
43
|
+
max_value = BigDecimal('2')**BigDecimal('256')
|
44
|
+
(parsed_value / max_value).round(16)
|
52
45
|
end
|
53
46
|
end
|
54
47
|
|
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.8.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-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -78,6 +78,7 @@ 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
|
82
83
|
- lib/kameleoon/configuration/rule.rb
|
83
84
|
- lib/kameleoon/configuration/settings.rb
|
@@ -95,6 +96,9 @@ files:
|
|
95
96
|
- lib/kameleoon/data/manager/assigned_variation.rb
|
96
97
|
- lib/kameleoon/data/manager/data_array_storage.rb
|
97
98
|
- lib/kameleoon/data/manager/data_map_storage.rb
|
99
|
+
- lib/kameleoon/data/manager/forced_experiment_variation.rb
|
100
|
+
- lib/kameleoon/data/manager/forced_feature_variation.rb
|
101
|
+
- lib/kameleoon/data/manager/forced_variation.rb
|
98
102
|
- lib/kameleoon/data/manager/page_view_visit.rb
|
99
103
|
- lib/kameleoon/data/manager/visitor.rb
|
100
104
|
- lib/kameleoon/data/manager/visitor_manager.rb
|