kameleoon-client-ruby 3.12.1 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kameleoon/configuration/custom_data_info.rb +7 -1
- data/lib/kameleoon/configuration/data_file.rb +31 -11
- data/lib/kameleoon/configuration/feature_flag.rb +8 -4
- data/lib/kameleoon/configuration/rule.rb +5 -6
- data/lib/kameleoon/data/cookie.rb +0 -1
- data/lib/kameleoon/data/data.rb +1 -0
- data/lib/kameleoon/data/device.rb +1 -1
- data/lib/kameleoon/data/manager/visitor.rb +16 -4
- data/lib/kameleoon/data/visitor_visits.rb +64 -11
- data/lib/kameleoon/kameleoon_client.rb +37 -26
- data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +3 -3
- data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +20 -14
- data/lib/kameleoon/network/content_type.rb +1 -0
- data/lib/kameleoon/network/fetched_configuration.rb +14 -0
- data/lib/kameleoon/network/net_provider.rb +4 -3
- data/lib/kameleoon/network/network_manager.rb +25 -11
- data/lib/kameleoon/network/request.rb +1 -1
- data/lib/kameleoon/network/response.rb +5 -4
- data/lib/kameleoon/network/url_provider.rb +2 -2
- data/lib/kameleoon/targeting/condition.rb +0 -1
- data/lib/kameleoon/targeting/condition_factory.rb +0 -3
- data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +6 -5
- data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +16 -4
- data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +4 -3
- data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +5 -4
- data/lib/kameleoon/targeting/models.rb +4 -3
- data/lib/kameleoon/targeting/targeting_manager.rb +3 -4
- data/lib/kameleoon/version.rb +1 -1
- metadata +3 -3
- data/lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5566ec9ff02ebb63ff3ee337f4c26fc0c229f2de8df0893fe85dc3f83fbc0ce
|
4
|
+
data.tar.gz: fcd11bacbaead7a0c357a0fd4828fb6c4b7347ebf1fab728f4e00882b0224dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78418ebd561b9d1fe2639751d0dae68a875a4a76f4782ac079fe2f3f3c6e2b7466c41cb762e978b4895973a97ddf4940b10171d36c2695a565ed2e1b66445f53
|
7
|
+
data.tar.gz: b91b577c23c13547d78c09c98a2c39bb788a314b280b50c57bf1cc25891dd74747127aec2bc14b0a5a4908a2cde2d0e2089ec13c6f5200087b7c52bd80b7a68f
|
@@ -13,16 +13,18 @@ module Kameleoon
|
|
13
13
|
def initialize(hashes)
|
14
14
|
@local_only = Set[]
|
15
15
|
@visitor_scope = Set[]
|
16
|
+
@custom_data_index_by_id = {}
|
16
17
|
unless hashes.nil?
|
17
18
|
for hash in hashes
|
18
19
|
index = hash['index']
|
19
20
|
@local_only.add(index) if hash['localOnly']
|
20
21
|
@visitor_scope.add(index) if hash['scope'] == SCOPE_VISITOR
|
22
|
+
id = hash['id']
|
23
|
+
@custom_data_index_by_id[id] = index if id && index
|
21
24
|
if hash['isMappingIdentifier']
|
22
25
|
unless @mapping_identifier_index.nil?
|
23
26
|
Logging::KameleoonLogger.warning('More than one mapping identifier is set. Undefined behavior ' \
|
24
27
|
'may occur on cross-device reconciliation.')
|
25
|
-
|
26
28
|
end
|
27
29
|
@mapping_identifier_index = index
|
28
30
|
end
|
@@ -42,6 +44,10 @@ module Kameleoon
|
|
42
44
|
@visitor_scope.include?(index)
|
43
45
|
end
|
44
46
|
|
47
|
+
def get_custom_data_index_by_id(custom_data_id)
|
48
|
+
@custom_data_index_by_id[custom_data_id]
|
49
|
+
end
|
50
|
+
|
45
51
|
def self.mapping_identifier?(custom_data_info, custom_data)
|
46
52
|
!custom_data_info.nil? && (custom_data.id == custom_data_info.mapping_identifier_index) && \
|
47
53
|
!(custom_data.values.empty? || custom_data.values[0].empty?)
|
@@ -6,32 +6,41 @@ require 'kameleoon/configuration/feature_flag'
|
|
6
6
|
require 'kameleoon/configuration/me_group'
|
7
7
|
require 'kameleoon/configuration/settings'
|
8
8
|
require 'kameleoon/logging/kameleoon_logger'
|
9
|
+
require 'kameleoon/targeting/models'
|
9
10
|
|
10
11
|
module Kameleoon
|
11
12
|
module Configuration
|
12
13
|
class DataFile
|
13
|
-
attr_reader :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule,
|
14
|
-
:rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
|
14
|
+
attr_reader :last_modified, :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule,
|
15
|
+
:feature_flag_by_id, :rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
|
15
16
|
:experiment_ids_with_js_css_variable, :holdout
|
16
17
|
|
17
18
|
def to_s
|
18
19
|
'DataFile{' \
|
19
20
|
"environment:#{@environment}," \
|
21
|
+
"last_modified:#{@last_modified}," \
|
20
22
|
"feature_flags:#{@feature_flags.size}," \
|
21
23
|
"settings:#{@settings}" \
|
22
24
|
'}'
|
23
25
|
end
|
24
26
|
|
25
|
-
def initialize(environment, configuration = nil)
|
26
|
-
Logging::KameleoonLogger.debug(
|
27
|
+
def initialize(environment, configuration = nil, last_modified = nil)
|
28
|
+
Logging::KameleoonLogger.debug(
|
29
|
+
"CALL: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
|
30
|
+
environment, last_modified
|
31
|
+
)
|
27
32
|
@environment = environment
|
33
|
+
@last_modified = last_modified
|
28
34
|
if configuration.nil?
|
29
35
|
init_default
|
30
36
|
else
|
31
37
|
init(configuration)
|
32
38
|
end
|
33
39
|
collect_indices
|
34
|
-
Logging::KameleoonLogger.debug(
|
40
|
+
Logging::KameleoonLogger.debug(
|
41
|
+
"RETURN: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
|
42
|
+
environment, last_modified
|
43
|
+
)
|
35
44
|
end
|
36
45
|
|
37
46
|
def get_feature_flag(feature_key)
|
@@ -61,18 +70,29 @@ module Kameleoon
|
|
61
70
|
def init(configuration)
|
62
71
|
Logging::KameleoonLogger.debug('CALL: DataFile.init(configuration: %s)', configuration)
|
63
72
|
@settings = Settings.new(configuration['configuration'])
|
64
|
-
|
65
|
-
configuration['
|
66
|
-
|
67
|
-
@feature_flags[ff.feature_key] = ff
|
68
|
-
end
|
73
|
+
segments = parse_segments(configuration)
|
74
|
+
@custom_data_info = CustomDataInfo.new(configuration['customData'])
|
75
|
+
@feature_flags = parse_feature_flags(configuration, segments, @custom_data_info)
|
69
76
|
@me_groups = make_me_groups(@feature_flags)
|
70
77
|
@has_any_targeted_delivery_rule = any_targeted_delivery_rule?
|
71
|
-
@custom_data_info = CustomDataInfo.new(configuration['customData'])
|
72
78
|
@holdout = Experiment.from_json(configuration['holdout']) if configuration.include?('holdout')
|
73
79
|
Logging::KameleoonLogger.debug('RETURN: DataFile.init(configuration: %s)', configuration)
|
74
80
|
end
|
75
81
|
|
82
|
+
def parse_segments(configuration)
|
83
|
+
configuration['segments'].to_h do |raw_seg|
|
84
|
+
seg = Targeting::Segment.new(raw_seg)
|
85
|
+
[seg.id, seg]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_feature_flags(configuration, segments, cdi)
|
90
|
+
configuration['featureFlags'].to_h do |raw_ff|
|
91
|
+
ff = FeatureFlag.new(raw_ff, segments, cdi)
|
92
|
+
[ff.feature_key, ff]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
76
96
|
def any_targeted_delivery_rule?
|
77
97
|
@feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
|
78
98
|
end
|
@@ -8,7 +8,8 @@ module Kameleoon
|
|
8
8
|
module Configuration
|
9
9
|
# Class for manage all feature flags with rules
|
10
10
|
class FeatureFlag
|
11
|
-
|
11
|
+
attr_reader :id, :feature_key, :variations, :default_variation_key, :me_group_name, :environment_enabled, :rules,
|
12
|
+
:bucketing_custom_data_index
|
12
13
|
|
13
14
|
def self.create_from_array(array)
|
14
15
|
array&.map { |it| FeatureFlag.new(it) }
|
@@ -21,18 +22,21 @@ module Kameleoon
|
|
21
22
|
"environment_enabled:#{@environment_enabled}," \
|
22
23
|
"default_variation_key:'#{@default_variation_key}'," \
|
23
24
|
"me_group_name:'#{@me_group_name}'," \
|
24
|
-
"rules:#{@rules.size}" \
|
25
|
+
"rules:#{@rules.size}," \
|
26
|
+
"bucketing_custom_data_index:#{@bucketing_custom_data_index}" \
|
25
27
|
'}'
|
26
28
|
end
|
27
29
|
|
28
|
-
def initialize(hash)
|
30
|
+
def initialize(hash, segments, cdi)
|
29
31
|
@id = hash['id']
|
30
32
|
@feature_key = hash['featureKey']
|
31
33
|
@variations = Variation.create_from_array(hash['variations'])
|
32
34
|
@default_variation_key = hash['defaultVariationKey']
|
33
35
|
@me_group_name = hash['mutuallyExclusiveGroup']
|
34
36
|
@environment_enabled = hash['environmentEnabled']
|
35
|
-
@rules = Rule.create_from_array(hash['rules'])
|
37
|
+
@rules = Rule.create_from_array(hash['rules'], segments)
|
38
|
+
bucketing_cd_id = hash['bucketingCustomDataId']
|
39
|
+
@bucketing_custom_data_index = bucketing_cd_id ? cdi.get_custom_data_index_by_id(bucketing_cd_id) : nil
|
36
40
|
end
|
37
41
|
|
38
42
|
def get_variation_by_key(key)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'experiment'
|
4
|
-
require 'kameleoon/targeting/models'
|
5
4
|
|
6
5
|
module Kameleoon
|
7
6
|
# Module which contains all internal data of SDK
|
@@ -32,22 +31,22 @@ module Kameleoon
|
|
32
31
|
attr_reader :id, :order, :type, :exposition, :experiment, :respool_time, :segment_id
|
33
32
|
attr_accessor :targeting_segment
|
34
33
|
|
35
|
-
def self.create_from_array(array)
|
36
|
-
array&.map { |it| Rule.new(it) }
|
34
|
+
def self.create_from_array(array, segments)
|
35
|
+
array&.map { |it| Rule.new(it, segments) }
|
37
36
|
end
|
38
37
|
|
39
38
|
def to_s
|
40
39
|
"Rule{id:#{@id}}"
|
41
40
|
end
|
42
41
|
|
43
|
-
def initialize(hash)
|
42
|
+
def initialize(hash, segments)
|
44
43
|
@id = hash['id']
|
45
44
|
@order = hash['order']
|
46
45
|
@type = RuleType.from_literal(hash['type'])
|
47
46
|
@exposition = hash['exposition']
|
48
47
|
@respool_time = hash['respoolTime']
|
49
|
-
@
|
50
|
-
@
|
48
|
+
@segment_id = hash['segmentId'] || -1
|
49
|
+
@targeting_segment = segments[@segment_id] if @segment_id != -1
|
51
50
|
@experiment = Experiment.from_json(hash)
|
52
51
|
end
|
53
52
|
|
data/lib/kameleoon/data/data.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'concurrent'
|
4
|
+
require 'time'
|
4
5
|
require 'kameleoon/data/browser'
|
5
6
|
require 'kameleoon/data/cbscores'
|
6
7
|
require 'kameleoon/data/conversion'
|
@@ -42,6 +43,10 @@ module Kameleoon
|
|
42
43
|
update_last_activity_time
|
43
44
|
end
|
44
45
|
|
46
|
+
def time_started
|
47
|
+
@data.time_started
|
48
|
+
end
|
49
|
+
|
45
50
|
def last_activity_time
|
46
51
|
@data.last_activity_time
|
47
52
|
end
|
@@ -263,7 +268,7 @@ module Kameleoon
|
|
263
268
|
when CBScores
|
264
269
|
@data.set_cbscores(data, overwrite)
|
265
270
|
when VisitorVisits
|
266
|
-
@data.
|
271
|
+
@data.set_visitor_visits(data, overwrite)
|
267
272
|
when UniqueIdentifier
|
268
273
|
@is_unique_identifier = data.value
|
269
274
|
else
|
@@ -286,12 +291,13 @@ module Kameleoon
|
|
286
291
|
end
|
287
292
|
|
288
293
|
class VisitorData
|
289
|
-
attr_reader :mutex, :device, :browser, :geolocation, :operating_system
|
290
|
-
attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores,
|
294
|
+
attr_reader :time_started, :mutex, :device, :browser, :geolocation, :operating_system, :visitor_visits
|
295
|
+
attr_accessor :last_activity_time, :legal_consent, :user_agent, :cookie, :kcs_heat, :cbscores,
|
291
296
|
:mapping_identifier, :forced_variations, :simulated_variations
|
292
297
|
|
293
298
|
def initialize
|
294
299
|
Logging::KameleoonLogger.debug('CALL: VisitorData.new')
|
300
|
+
@time_started = (Time.now.to_f * 1000).to_i
|
295
301
|
@mutex = Concurrent::ReadWriteLock.new
|
296
302
|
@legal_consent = false
|
297
303
|
Logging::KameleoonLogger.debug('RETURN: VisitorData.new')
|
@@ -318,6 +324,7 @@ module Kameleoon
|
|
318
324
|
blk.call(@browser) unless @browser.nil?
|
319
325
|
blk.call(@operating_system) unless @operating_system.nil?
|
320
326
|
blk.call(@geolocation) unless @geolocation.nil?
|
327
|
+
blk.call(@visitor_visits) unless @visitor_visits.nil?
|
321
328
|
@mutex.with_read_lock do
|
322
329
|
@custom_data_map&.each { |_, cd| blk.call(cd) }
|
323
330
|
@page_view_visits&.each { |_, pvv| blk.call(pvv.page_view) }
|
@@ -332,8 +339,9 @@ module Kameleoon
|
|
332
339
|
@mutex.with_read_lock do
|
333
340
|
count += 1 unless @device.nil?
|
334
341
|
count += 1 unless @browser.nil?
|
335
|
-
count += 1 unless @geolocation.nil?
|
336
342
|
count += 1 unless @operating_system.nil?
|
343
|
+
count += 1 unless @geolocation.nil?
|
344
|
+
count += 1 unless @visitor_visits.nil?
|
337
345
|
count += @custom_data_map.size unless @custom_data_map.nil?
|
338
346
|
count += @page_view_visits.size unless @page_view_visits.nil?
|
339
347
|
count += @conversions.size unless @conversions.nil?
|
@@ -433,6 +441,10 @@ module Kameleoon
|
|
433
441
|
@cbscores = cbs if overwrite || @cbscores.nil?
|
434
442
|
end
|
435
443
|
|
444
|
+
def set_visitor_visits(visitor_visits, overwrite)
|
445
|
+
@visitor_visits = visitor_visits.localize(@time_started) if overwrite || @visitor_visits.nil?
|
446
|
+
end
|
447
|
+
|
436
448
|
def add_forced_feature_variation(forced_variation)
|
437
449
|
@simulated_variations ||= {}
|
438
450
|
@simulated_variations[forced_variation.feature_key] = forced_variation
|
@@ -1,24 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kameleoon/data/data'
|
4
|
+
require 'kameleoon/network/uri_helper'
|
5
|
+
|
3
6
|
module Kameleoon
|
4
|
-
class VisitorVisits
|
5
|
-
attr_reader :
|
7
|
+
class VisitorVisits < DuplicationUnsafeData
|
8
|
+
attr_reader :visit_number, :prev_visits, :time_started, :time_since_previous_visit
|
6
9
|
|
7
|
-
def
|
8
|
-
|
10
|
+
def initialize(prev_visits, visit_number = 0, time_started: 0, time_since_previous_visit: 0)
|
11
|
+
super(DataType::VISITOR_VISITS)
|
12
|
+
@visit_number = visit_number >= prev_visits.size ? visit_number : prev_visits.size
|
13
|
+
@prev_visits = prev_visits
|
14
|
+
@prev_visits.freeze
|
15
|
+
@time_started = time_started
|
16
|
+
@time_since_previous_visit = time_since_previous_visit
|
17
|
+
end
|
18
|
+
|
19
|
+
def localize(time_started)
|
20
|
+
time_since_previous_visit = 0
|
21
|
+
@prev_visits.each do |visit|
|
22
|
+
time_delta = time_started - visit.time_last_activity
|
23
|
+
unless time_delta.negative?
|
24
|
+
time_since_previous_visit = time_delta
|
25
|
+
break
|
26
|
+
end
|
27
|
+
end
|
28
|
+
VisitorVisits.new(
|
29
|
+
@prev_visits,
|
30
|
+
@visit_number,
|
31
|
+
time_started: time_started,
|
32
|
+
time_since_previous_visit: time_since_previous_visit
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def obtain_full_post_text_line
|
37
|
+
params = {
|
38
|
+
eventType: 'staticData',
|
39
|
+
visitNumber: @visit_number,
|
40
|
+
timeSincePreviousVisit: @time_since_previous_visit,
|
41
|
+
nonce: nonce
|
42
|
+
}
|
43
|
+
Network::UriHelper.encode_query(params)
|
9
44
|
end
|
10
45
|
|
11
|
-
def
|
12
|
-
|
13
|
-
@
|
46
|
+
def to_s
|
47
|
+
'VisitorVisits{' \
|
48
|
+
"visit_number:#{@visit_number}," \
|
49
|
+
"prev_visits:#{@prev_visits.map(&:to_s)}," \
|
50
|
+
"time_started:#{@time_started}," \
|
51
|
+
"time_since_previous_visit:#{@time_since_previous_visit}" \
|
52
|
+
'}'
|
14
53
|
end
|
15
54
|
|
16
|
-
def
|
17
|
-
|
55
|
+
def ==(other)
|
56
|
+
other.is_a?(VisitorVisits) && (@visit_number == other.visit_number) && (@prev_visits == other.prev_visits) &&
|
57
|
+
(@time_started == other.time_started) && (@time_since_previous_visit == other.time_since_previous_visit)
|
18
58
|
end
|
19
59
|
|
20
|
-
|
21
|
-
|
60
|
+
class Visit
|
61
|
+
attr_reader :time_started, :time_last_activity
|
62
|
+
|
63
|
+
def initialize(time_started, time_last_activity = nil)
|
64
|
+
@time_started = time_started
|
65
|
+
@time_last_activity = time_last_activity || time_started
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"Visit{time_started:#{@time_started},time_last_activity:#{time_last_activity}}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def ==(other)
|
73
|
+
other.is_a?(Visit) && (@time_started == other.time_started) && (@time_last_activity == other.time_last_activity)
|
74
|
+
end
|
22
75
|
end
|
23
76
|
end
|
24
77
|
end
|
@@ -852,14 +852,17 @@ module Kameleoon
|
|
852
852
|
|
853
853
|
def obtain_configuration(time_stamp = nil)
|
854
854
|
Logging::KameleoonLogger.info('Fetching configuration from Client-Config service')
|
855
|
-
|
855
|
+
last_modified = @data_manager.data_file.last_modified
|
856
|
+
response = @network_manager.fetch_configuration(time_stamp, if_modified_since: last_modified)
|
856
857
|
return false unless response
|
857
858
|
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
859
|
+
if response.configuration
|
860
|
+
configuration = JSON.parse(response.configuration)
|
861
|
+
data_file = Configuration::DataFile.new(@config.environment, configuration, response.last_modified)
|
862
|
+
apply_new_configuration(data_file)
|
863
|
+
call_update_handler_if_needed(!time_stamp.nil?)
|
864
|
+
Logging::KameleoonLogger.info('Feature flags are fetched: %s', response.inspect)
|
865
|
+
end
|
863
866
|
true
|
864
867
|
end
|
865
868
|
|
@@ -871,7 +874,7 @@ module Kameleoon
|
|
871
874
|
end
|
872
875
|
|
873
876
|
##
|
874
|
-
# Call the handler when
|
877
|
+
# Call the handler when configuration was updated with new time stamp.
|
875
878
|
#
|
876
879
|
# @param need_call [Bool] Indicates if we need to call handler or not.
|
877
880
|
def call_update_handler_if_needed(need_call)
|
@@ -930,7 +933,7 @@ module Kameleoon
|
|
930
933
|
forced_variation = visitor&.get_forced_feature_variation(feature_flag.feature_key)
|
931
934
|
if forced_variation
|
932
935
|
eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
|
933
|
-
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save) && \
|
936
|
+
elsif visitor_not_in_holdout?(visitor, visitor_code, track, save, feature_flag.bucketing_custom_data_index) && \
|
934
937
|
ff_unrestricted_by_me_group?(visitor, visitor_code, feature_flag)
|
935
938
|
eval_exp = _calculate_variation_key_for_feature(visitor_code, feature_flag)
|
936
939
|
end
|
@@ -952,7 +955,7 @@ module Kameleoon
|
|
952
955
|
unrestricted = true
|
953
956
|
me_group = @data_manager.data_file.me_groups[feature_flag.me_group_name]
|
954
957
|
if me_group
|
955
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
958
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
956
959
|
me_group_hash = Utils::Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
|
957
960
|
Logging::KameleoonLogger.debug(
|
958
961
|
"Calculated ME group hash %s for code: '%s', meGroup: '%s'",
|
@@ -967,17 +970,17 @@ module Kameleoon
|
|
967
970
|
unrestricted
|
968
971
|
end
|
969
972
|
|
970
|
-
def visitor_not_in_holdout?(visitor, visitor_code, track, save)
|
973
|
+
def visitor_not_in_holdout?(visitor, visitor_code, track, save, bucketing_custom_data_index)
|
971
974
|
holdout = @data_manager.data_file.holdout
|
972
975
|
return true if holdout.nil?
|
973
976
|
|
974
977
|
in_holdout_variation_key = 'in-holdout'
|
975
978
|
Logging::KameleoonLogger.debug(
|
976
|
-
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s
|
977
|
-
visitor_code, track, save
|
979
|
+
"CALL: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
|
980
|
+
'bucketing_custom_data_index: %s)', visitor_code, track, save, bucketing_custom_data_index
|
978
981
|
)
|
979
982
|
is_not_in_holdout = true
|
980
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
983
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
981
984
|
variation_hash = Utils::Hasher.obtain(code_for_hash, holdout.id)
|
982
985
|
Logging::KameleoonLogger.debug("Calculated holdout hash %s for code '%s'", variation_hash, code_for_hash)
|
983
986
|
var_by_exp = holdout.get_variation(variation_hash)
|
@@ -989,9 +992,9 @@ module Kameleoon
|
|
989
992
|
end
|
990
993
|
end
|
991
994
|
Logging::KameleoonLogger.debug(
|
992
|
-
"RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s
|
993
|
-
' -> (is_not_in_holdout: %s)',
|
994
|
-
visitor_code, track, save, is_not_in_holdout
|
995
|
+
"RETURN: KameleoonClient.visitor_not_in_holdout?(visitor, visitor_code: '%s', track: %s, save: %s, " \
|
996
|
+
'bucketing_custom_data_index: %s) -> (is_not_in_holdout: %s)',
|
997
|
+
visitor_code, track, save, bucketing_custom_data_index, is_not_in_holdout
|
995
998
|
)
|
996
999
|
is_not_in_holdout
|
997
1000
|
end
|
@@ -1035,16 +1038,24 @@ module Kameleoon
|
|
1035
1038
|
)
|
1036
1039
|
end
|
1037
1040
|
|
1038
|
-
def get_code_for_hash(visitor, visitor_code)
|
1039
|
-
|
1040
|
-
|
1041
|
+
def get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
1042
|
+
return visitor_code if visitor.nil?
|
1043
|
+
|
1044
|
+
# 1. Try to use the bucketing custom data's value if bucketingCustomDataId is defined
|
1045
|
+
if bucketing_custom_data_index
|
1046
|
+
bucketing_custom_data = visitor.custom_data.get(bucketing_custom_data_index)
|
1047
|
+
return bucketing_custom_data.values[0] if bucketing_custom_data && !bucketing_custom_data.values.empty?
|
1048
|
+
end
|
1049
|
+
# 2. Use mappingIdentifier instead of visitorCode if it was set up
|
1050
|
+
visitor.mapping_identifier || visitor_code
|
1041
1051
|
end
|
1042
1052
|
|
1043
|
-
def evaluate_cbscores(visitor, visitor_code, rule)
|
1053
|
+
def evaluate_cbscores(visitor, visitor_code, rule, bucketing_custom_data_index)
|
1044
1054
|
return nil if visitor&.cbscores.nil?
|
1045
1055
|
|
1046
1056
|
Logging::KameleoonLogger.debug(
|
1047
|
-
"CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s
|
1057
|
+
"CALL: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s, " \
|
1058
|
+
'bucketing_custom_data_index: %s)', visitor_code, rule, bucketing_custom_data_index
|
1048
1059
|
)
|
1049
1060
|
eval_exp = nil
|
1050
1061
|
var_id_group_by_scores = visitor.cbscores.values[rule.experiment.id]
|
@@ -1059,7 +1070,7 @@ module Kameleoon
|
|
1059
1070
|
if (var_by_exp_in_cbs&.size || 0).positive?
|
1060
1071
|
size = var_by_exp_in_cbs.size
|
1061
1072
|
if size > 1
|
1062
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
1073
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, bucketing_custom_data_index)
|
1063
1074
|
variation_hash = Utils::Hasher.obtain(code_for_hash, rule.experiment.id, rule.respool_time)
|
1064
1075
|
Logging::KameleoonLogger.debug("Calculated CBS hash %s for code '%s'", variation_hash, code_for_hash)
|
1065
1076
|
idx = [(variation_hash * size).to_i, size - 1].min
|
@@ -1070,8 +1081,8 @@ module Kameleoon
|
|
1070
1081
|
end
|
1071
1082
|
end
|
1072
1083
|
Logging::KameleoonLogger.debug(
|
1073
|
-
"RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s
|
1074
|
-
visitor_code, rule, eval_exp
|
1084
|
+
"RETURN: KameleoonClient.evaluate_cbscores(visitor, visitor_code: '%s', rule: %s," \
|
1085
|
+
'bucketing_custom_data_index: %s) -> (eval_exp: %s)', visitor_code, rule, bucketing_custom_data_index, eval_exp
|
1075
1086
|
)
|
1076
1087
|
eval_exp
|
1077
1088
|
end
|
@@ -1084,7 +1095,7 @@ module Kameleoon
|
|
1084
1095
|
visitor_code, feature_flag
|
1085
1096
|
)
|
1086
1097
|
visitor = @visitor_manager.get_visitor(visitor_code)
|
1087
|
-
code_for_hash = get_code_for_hash(visitor, visitor_code)
|
1098
|
+
code_for_hash = get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
1088
1099
|
# no rules -> return default_variation_key
|
1089
1100
|
eval_exp = nil
|
1090
1101
|
feature_flag.rules.each do |rule|
|
@@ -1107,7 +1118,7 @@ module Kameleoon
|
|
1107
1118
|
Logging::KameleoonLogger.debug("Calculated rule hash %s for code '%s'", hash_rule, code_for_hash)
|
1108
1119
|
# check main expostion for rule with hashRule
|
1109
1120
|
if hash_rule <= rule.exposition
|
1110
|
-
eval_exp = evaluate_cbscores(visitor, visitor_code, rule)
|
1121
|
+
eval_exp = evaluate_cbscores(visitor, visitor_code, rule, feature_flag.bucketing_custom_data_index)
|
1111
1122
|
break unless eval_exp.nil?
|
1112
1123
|
|
1113
1124
|
if rule.targeted_delivery_type?
|
@@ -42,7 +42,7 @@ module Kameleoon
|
|
42
42
|
filter = Types::RemoteVisitorDataFilter.new unless filter.is_a?(Types::RemoteVisitorDataFilter)
|
43
43
|
is_unique_identifier = @visitor_manger.get_visitor(visitor_code)&.is_unique_identifier || false
|
44
44
|
response = @network_manager.get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout)
|
45
|
-
remote_visitor_data = parse_custom_data_array(visitor_code, response)
|
45
|
+
remote_visitor_data = parse_custom_data_array(visitor_code, response, filter)
|
46
46
|
remote_visitor_data.mark_data_as_sent(@data_manager.data_file.custom_data_info)
|
47
47
|
data_to_add = remote_visitor_data.collect_data_to_add
|
48
48
|
if add_data && !data_to_add.empty?
|
@@ -66,8 +66,8 @@ module Kameleoon
|
|
66
66
|
|
67
67
|
##
|
68
68
|
# helper method used by `get_remote_visitor_data`
|
69
|
-
def parse_custom_data_array(visitor_code, response)
|
70
|
-
RemoteVisitorData.new(JSON.parse(response))
|
69
|
+
def parse_custom_data_array(visitor_code, response, filter)
|
70
|
+
RemoteVisitorData.new(JSON.parse(response), filter)
|
71
71
|
rescue StandardError => e
|
72
72
|
Logging::KameleoonLogger.error("Parsing of remote visitor data of '#{visitor_code}' failed: #{e}")
|
73
73
|
raise
|
@@ -18,21 +18,23 @@ module Kameleoon
|
|
18
18
|
module RemoteData
|
19
19
|
class RemoteVisitorData
|
20
20
|
attr_reader :custom_data_dict, :page_view_visits, :conversions, :experiments, :personalizations, :device,
|
21
|
-
:browser, :operating_system, :geolocation, :
|
21
|
+
:browser, :operating_system, :geolocation, :visitor_visits, :kcs_heat, :cbs, :visitor_code
|
22
22
|
|
23
|
-
def initialize(hash)
|
23
|
+
def initialize(hash, filter)
|
24
|
+
@filter = filter
|
25
|
+
@visit_number = 0
|
24
26
|
current_visit = hash['currentVisit']
|
25
|
-
parse_visit(current_visit) unless current_visit.nil?
|
27
|
+
parse_visit(current_visit, false) unless current_visit.nil?
|
26
28
|
previous_visits = hash['previousVisits']
|
27
29
|
previous_visits = [] if previous_visits.nil?
|
28
30
|
|
29
31
|
if previous_visits.size.positive?
|
30
|
-
|
32
|
+
prev_visits = []
|
31
33
|
previous_visits.each do |visit|
|
32
|
-
|
33
|
-
parse_visit(visit)
|
34
|
+
prev_visits.push(VisitorVisits::Visit.new(visit['timeStarted'] || 0, visit['timeLastEvent']))
|
35
|
+
parse_visit(visit, true)
|
34
36
|
end
|
35
|
-
@
|
37
|
+
@visitor_visits = VisitorVisits.new(prev_visits, @visit_number)
|
36
38
|
end
|
37
39
|
@kcs_heat = parse_kcs_heat(hash['kcs'])
|
38
40
|
@cbs = parse_cbscores(hash['cbs'])
|
@@ -41,7 +43,7 @@ module Kameleoon
|
|
41
43
|
def collect_data_to_add
|
42
44
|
data_to_add = []
|
43
45
|
data_to_add.concat(@custom_data_dict.values) unless @custom_data_dict.nil?
|
44
|
-
data_to_add.push(@
|
46
|
+
data_to_add.push(@visitor_visits) unless @visitor_visits.nil?
|
45
47
|
data_to_add.push(@kcs_heat) unless @kcs_heat.nil?
|
46
48
|
data_to_add.push(@cbs) unless @cbs.nil?
|
47
49
|
data_to_add.concat(@page_view_visits.values) unless @page_view_visits.nil?
|
@@ -72,7 +74,7 @@ module Kameleoon
|
|
72
74
|
|
73
75
|
private
|
74
76
|
|
75
|
-
def parse_visit(hash)
|
77
|
+
def parse_visit(hash, is_prev_visit)
|
76
78
|
@visitor_code = hash['visitorCode'] if @visitor_code.nil?
|
77
79
|
custom_data_events = hash['customDataEvents']
|
78
80
|
parse_custom_data(custom_data_events) if !custom_data_events.nil? && custom_data_events.size.positive?
|
@@ -86,7 +88,7 @@ module Kameleoon
|
|
86
88
|
@geolocation = parse_geolocation(geolocation_events) if @geolocation.nil? && !geolocation_events.nil? && \
|
87
89
|
geolocation_events.size.positive?
|
88
90
|
static_data_events = hash['staticDataEvent']
|
89
|
-
parse_static_data(static_data_events) unless static_data_events.nil?
|
91
|
+
parse_static_data(static_data_events, is_prev_visit) unless static_data_events.nil?
|
90
92
|
personalization_events = hash['personalizationEvents']
|
91
93
|
parse_personalizations(personalization_events) unless personalization_events.nil?
|
92
94
|
end
|
@@ -144,17 +146,21 @@ module Kameleoon
|
|
144
146
|
Geolocation.new(data['country'], data['region'], data['city'])
|
145
147
|
end
|
146
148
|
|
147
|
-
def parse_static_data(static_data_event)
|
149
|
+
def parse_static_data(static_data_event, is_prev_visit)
|
148
150
|
data = static_data_event['data']
|
149
|
-
if @
|
151
|
+
if @visit_number.zero?
|
152
|
+
@visit_number = data['visitNumber'] || 0
|
153
|
+
@visit_number += 1 if is_prev_visit && @visit_number.positive?
|
154
|
+
end
|
155
|
+
if @filter.device && @device.nil?
|
150
156
|
device_type = data['deviceType']
|
151
157
|
@device = Device.new(device_type) unless device_type.nil?
|
152
158
|
end
|
153
|
-
if @browser.nil?
|
159
|
+
if @filter.browser && @browser.nil?
|
154
160
|
browser_type = BrowserType.from_name(data['browser'])
|
155
161
|
@browser = Browser.new(browser_type, data['browserVersion']) unless browser_type.nil?
|
156
162
|
end
|
157
|
-
if @operating_system.nil?
|
163
|
+
if @filter.operating_system && @operating_system.nil?
|
158
164
|
operating_system_type = OperatingSystemType.from_name(data['os'])
|
159
165
|
@operating_system = OperatingSystem.new(operating_system_type) unless operating_system_type.nil?
|
160
166
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Network
|
5
|
+
class FetchedConfiguration
|
6
|
+
attr_reader :configuration, :last_modified
|
7
|
+
|
8
|
+
def initialize(configuration, last_modified)
|
9
|
+
@configuration = configuration
|
10
|
+
@last_modified = last_modified
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -22,7 +22,7 @@ module Kameleoon
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def unknown_method_response(method, request)
|
25
|
-
Response.new("Unknown request method '#{method}'", nil, nil, request)
|
25
|
+
Response.new("Unknown request method '#{method}'", nil, nil, nil, request)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -48,9 +48,10 @@ module Kameleoon
|
|
48
48
|
end
|
49
49
|
body = resp.body
|
50
50
|
body = nil if body&.empty?
|
51
|
-
|
51
|
+
headers = resp.to_hash
|
52
|
+
Response.new(nil, resp.code.to_i, body, headers, request)
|
52
53
|
rescue StandardError => e
|
53
|
-
Response.new(e, nil, nil, request)
|
54
|
+
Response.new(e, nil, nil, nil, request)
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'kameleoon/logging/kameleoon_logger'
|
4
4
|
require 'kameleoon/network/content_type'
|
5
|
+
require 'kameleoon/network/fetched_configuration'
|
5
6
|
require 'kameleoon/network/method'
|
6
7
|
require 'kameleoon/network/request'
|
7
8
|
require 'kameleoon/network/net_provider'
|
@@ -19,6 +20,8 @@ module Kameleoon
|
|
19
20
|
SDK_TYPE_HEADER = 'X-Kameleoon-SDK-Type'
|
20
21
|
SDK_VERSION_HEADER = 'X-Kameleoon-SDK-Version'
|
21
22
|
ACCESS_TOKEN_GRANT_TYPE = 'client_credentials'
|
23
|
+
HEADER_IF_MODIFIED_SINCE = 'If-Modified-Since'
|
24
|
+
HEADER_LAST_MODIFIED = 'last-modified' # in lower case because the network lib casts response headers to lower
|
22
25
|
|
23
26
|
attr_reader :environment, :default_timeout, :access_token_source, :url_provider
|
24
27
|
|
@@ -30,26 +33,32 @@ module Kameleoon
|
|
30
33
|
@sync_net_provider = SyncNetProvider.new
|
31
34
|
end
|
32
35
|
|
33
|
-
def fetch_configuration(timestamp = nil, timeout = nil)
|
36
|
+
def fetch_configuration(timestamp = nil, timeout = nil, if_modified_since: nil)
|
34
37
|
url = @url_provider.make_configuration_url(@environment, timestamp)
|
35
38
|
timeout = ensure_timeout(timeout)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
headers = { SDK_TYPE_HEADER => SDK_NAME, SDK_VERSION_HEADER => SDK_VERSION }
|
40
|
+
headers[HEADER_IF_MODIFIED_SINCE] = if_modified_since if if_modified_since
|
41
|
+
request = Request.new(Method::GET, url, ContentType::JSON, timeout, extra_headers: headers)
|
42
|
+
response, success = make_call(request, false, FETCH_CONFIGURATION_ATTEMPT_NUMBER - 1)
|
43
|
+
return nil unless success
|
44
|
+
return FetchedConfiguration.new(nil, nil) if response.code == 304
|
45
|
+
|
46
|
+
last_modified = response.headers&.[](HEADER_LAST_MODIFIED)&.first
|
47
|
+
FetchedConfiguration.new(response.body, last_modified)
|
39
48
|
end
|
40
49
|
|
41
50
|
def get_remote_data(key, timeout = nil)
|
42
51
|
url = @url_provider.make_api_data_get_request_url(key)
|
43
52
|
timeout = ensure_timeout(timeout)
|
44
53
|
request = Request.new(Method::GET, url, ContentType::JSON, timeout)
|
45
|
-
make_call(request, true)
|
54
|
+
unwrap_response(*make_call(request, true))
|
46
55
|
end
|
47
56
|
|
48
57
|
def get_remote_visitor_data(visitor_code, filter, is_unique_identifier, timeout = nil)
|
49
58
|
url = @url_provider.make_visitor_data_get_url(visitor_code, filter, is_unique_identifier)
|
50
59
|
timeout = ensure_timeout(timeout)
|
51
60
|
request = Request.new(Method::GET, url, ContentType::JSON, timeout)
|
52
|
-
make_call(request, true)
|
61
|
+
unwrap_response(*make_call(request, true))
|
53
62
|
end
|
54
63
|
|
55
64
|
def send_tracking_data(lines, timeout = nil)
|
@@ -57,8 +66,8 @@ module Kameleoon
|
|
57
66
|
|
58
67
|
url = @url_provider.make_tracking_url
|
59
68
|
timeout = ensure_timeout(timeout)
|
60
|
-
request = Request.new(Method::POST, url, ContentType::
|
61
|
-
make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY)
|
69
|
+
request = Request.new(Method::POST, url, ContentType::WILDCARD, timeout, data: lines)
|
70
|
+
unwrap_response(*make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY))
|
62
71
|
end
|
63
72
|
|
64
73
|
def fetch_access_jwtoken(client_id, client_secret, timeout = nil)
|
@@ -71,7 +80,7 @@ module Kameleoon
|
|
71
80
|
}
|
72
81
|
data = UriHelper.encode_query(data_map).encode('UTF-8')
|
73
82
|
request = Request.new(Method::POST, url, ContentType::FORM, timeout, data: data)
|
74
|
-
make_call(request, false)
|
83
|
+
unwrap_response(*make_call(request, false))
|
75
84
|
end
|
76
85
|
|
77
86
|
private
|
@@ -81,6 +90,7 @@ module Kameleoon
|
|
81
90
|
request, try_access_token_auth, retry_limit, retry_delay)
|
82
91
|
attempt = 0
|
83
92
|
success = false
|
93
|
+
response = nil
|
84
94
|
while !success && (attempt <= retry_limit)
|
85
95
|
delay(retry_delay) if attempt.positive? && retry_delay.positive?
|
86
96
|
try_authorize(request) if try_access_token_auth
|
@@ -110,11 +120,11 @@ module Kameleoon
|
|
110
120
|
attempt += 1
|
111
121
|
end
|
112
122
|
Logging::KameleoonLogger.debug('Fetched response %s for request %s', response, request)
|
113
|
-
|
123
|
+
[response, success]
|
114
124
|
end
|
115
125
|
|
116
126
|
def get_log_level(attempt, attempt_count)
|
117
|
-
|
127
|
+
attempt < attempt_count ? Logging::LogLevel::WARNING : Logging::LogLevel::ERROR
|
118
128
|
end
|
119
129
|
|
120
130
|
def delay(period)
|
@@ -129,6 +139,10 @@ module Kameleoon
|
|
129
139
|
token = @access_token_source.get_token(request.timeout)
|
130
140
|
request.authorize(token)
|
131
141
|
end
|
142
|
+
|
143
|
+
def unwrap_response(response, success)
|
144
|
+
success ? response.body : false
|
145
|
+
end
|
132
146
|
end
|
133
147
|
end
|
134
148
|
end
|
@@ -16,7 +16,7 @@ module Kameleoon
|
|
16
16
|
body = @data
|
17
17
|
end
|
18
18
|
end
|
19
|
-
"
|
19
|
+
"Request{method:'#{@method}',url:'#{@url}',headers:#{@extra_headers},body:'#{body}'}"
|
20
20
|
end
|
21
21
|
|
22
22
|
def initialize(method, url, content_type, timeout, extra_headers: nil, data: nil)
|
@@ -5,21 +5,22 @@ module Kameleoon
|
|
5
5
|
##
|
6
6
|
# Response represents HTTP response.
|
7
7
|
class Response
|
8
|
-
attr_reader :error, :code, :body, :request
|
8
|
+
attr_reader :error, :code, :body, :headers, :request
|
9
9
|
|
10
10
|
def to_s
|
11
|
-
"
|
11
|
+
"Response{code:'#{@code}',error:'#{@error}',body:#{@body}}"
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(error, code, body, request)
|
14
|
+
def initialize(error, code, body, headers, request)
|
15
15
|
@error = error
|
16
16
|
@code = code
|
17
17
|
@body = body
|
18
|
+
@headers = headers
|
18
19
|
@request = request
|
19
20
|
end
|
20
21
|
|
21
22
|
def success?
|
22
|
-
@error.nil? && ((@code / 100 == 2) || (@code == 403))
|
23
|
+
@error.nil? && ((@code / 100 == 2) || (@code == 403) || (@code == 304))
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -19,7 +19,7 @@ module Kameleoon
|
|
19
19
|
DEFAULT_ACCESS_TOKEN_DOMAIN = 'api.kameleoon.com'
|
20
20
|
DEFAULT_DATA_API_DOMAIN = 'data.kameleoon.io'
|
21
21
|
|
22
|
-
CONFIGURATION_API_URL_FORMAT = 'https://%s/%s'
|
22
|
+
CONFIGURATION_API_URL_FORMAT = 'https://%s/v3/%s'
|
23
23
|
DATA_API_URL_FORMAT = 'https://%s%s?%s'
|
24
24
|
RT_CONFIGURATION_URL_FORMAT = 'https://%s:8110/sse?%s'
|
25
25
|
ACCESS_TOKEN_URL_FORMAT = 'https://%s/oauth/token'
|
@@ -68,6 +68,7 @@ module Kameleoon
|
|
68
68
|
params[is_unique_identifier ? :mappingValue : :visitorCode] = visitor_code
|
69
69
|
params[:maxNumberPreviousVisits] = filter.previous_visit_amount
|
70
70
|
params[:version] = 0
|
71
|
+
params[:staticData] = true
|
71
72
|
params[:kcs] = true if filter.kcs
|
72
73
|
params[:currentVisit] = true if filter.current_visit
|
73
74
|
params[:customData] = true if filter.custom_data
|
@@ -75,7 +76,6 @@ module Kameleoon
|
|
75
76
|
params[:geolocation] = true if filter.geolocation
|
76
77
|
params[:experiment] = true if filter.experiments
|
77
78
|
params[:page] = true if filter.page_views
|
78
|
-
params[:staticData] = true if filter.device || filter.browser || filter.operating_system
|
79
79
|
params[:personalization] = true if filter.personalization
|
80
80
|
params[:cbs] = true if filter.cbs
|
81
81
|
format(DATA_API_URL_FORMAT, @data_api_domain, VISITOR_DATA_PATH, UriHelper.encode_query(params))
|
@@ -11,7 +11,6 @@ module Kameleoon
|
|
11
11
|
TARGET_FEATURE_FLAG = 'TARGET_FEATURE_FLAG'
|
12
12
|
TARGET_EXPERIMENT = 'TARGET_EXPERIMENT'
|
13
13
|
TARGET_PERSONALIZATION = 'TARGET_PERSONALIZATION'
|
14
|
-
EXCLUSIVE_FEATURE_FLAG = 'EXCLUSIVE_FEATURE_FLAG'
|
15
14
|
EXCLUSIVE_EXPERIMENT = 'EXCLUSIVE_EXPERIMENT'
|
16
15
|
PAGE_URL = 'PAGE_URL'
|
17
16
|
PAGE_VIEWS = 'PAGE_VIEWS'
|
@@ -3,7 +3,6 @@ require_relative 'conditions/target_experiment_condition'
|
|
3
3
|
require_relative 'conditions/target_feature_flag_condition'
|
4
4
|
require_relative 'conditions/target_personalization_condition'
|
5
5
|
require_relative 'conditions/exclusive_experiment_condition'
|
6
|
-
require_relative 'conditions/exclusive_feature_flag_condition'
|
7
6
|
require_relative 'conditions/page_title_condition'
|
8
7
|
require_relative 'conditions/page_url_condition'
|
9
8
|
require_relative 'conditions/page_view_number_condition'
|
@@ -39,8 +38,6 @@ module Kameleoon
|
|
39
38
|
TargetExperimentCondition.new(condition_json)
|
40
39
|
when ConditionType::TARGET_PERSONALIZATION
|
41
40
|
TargetPersonalizationCondition.new(condition_json)
|
42
|
-
when ConditionType::EXCLUSIVE_FEATURE_FLAG
|
43
|
-
ExclusiveFeatureFlagCondition.new(condition_json)
|
44
41
|
when ConditionType::EXCLUSIVE_EXPERIMENT
|
45
42
|
ExclusiveExperimentCondition.new(condition_json)
|
46
43
|
when ConditionType::PAGE_URL
|
@@ -10,16 +10,17 @@ module Kameleoon
|
|
10
10
|
def initialize(json_condition)
|
11
11
|
count_in_millis = json_condition['countInMillis']
|
12
12
|
super(json_condition, count_in_millis)
|
13
|
-
@is_first_visit = @type ==
|
13
|
+
@is_first_visit = @type == ConditionType::FIRST_VISIT
|
14
14
|
end
|
15
15
|
|
16
16
|
def check(data)
|
17
|
-
|
17
|
+
data ||= VisitorVisits.new([])
|
18
|
+
return false unless data.is_a?(VisitorVisits) && !@condition_value.nil?
|
18
19
|
|
19
|
-
|
20
|
-
if
|
20
|
+
prev_visits = data.prev_visits
|
21
|
+
if prev_visits.size >= 1
|
21
22
|
now = (Time.now.to_f * 1000).to_i # ... * 1000 for convert seconds to milliseconds
|
22
|
-
visit_time =
|
23
|
+
visit_time = prev_visits[@is_first_visit ? prev_visits.size - 1 : 0].time_started
|
23
24
|
return check_targeting(now - visit_time)
|
24
25
|
end
|
25
26
|
false
|
@@ -13,15 +13,27 @@ module Kameleoon
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def check(data)
|
16
|
-
return false unless
|
16
|
+
return false unless data.is_a?(TargetingData) && !@condition_value.nil?
|
17
17
|
|
18
18
|
number_of_visits_today = 0
|
19
19
|
start_of_day = (Time.new.to_date.to_time.to_f * 1000).to_i # ... * 1000 to convert seconds to milliseconds
|
20
|
-
|
21
|
-
break if
|
20
|
+
data.visitor_visits.prev_visits.each do |visit|
|
21
|
+
break if visit.time_started < start_of_day
|
22
|
+
|
22
23
|
number_of_visits_today += 1
|
23
24
|
end
|
24
|
-
|
25
|
+
number_of_visits_today += 1 if data.current_visit_time_started >= start_of_day
|
26
|
+
check_targeting(number_of_visits_today)
|
27
|
+
end
|
28
|
+
|
29
|
+
class TargetingData
|
30
|
+
attr_reader :current_visit_time_started, :visitor_visits
|
31
|
+
|
32
|
+
def initialize(current_visit_time_started, visitor_visits)
|
33
|
+
@current_visit_time_started =
|
34
|
+
current_visit_time_started.is_a?(Integer) ? current_visit_time_started : (Time.new.to_f * 1000).to_i
|
35
|
+
@visitor_visits = visitor_visits.is_a?(VisitorVisits) ? visitor_visits : VisitorVisits.new([])
|
36
|
+
end
|
25
37
|
end
|
26
38
|
end
|
27
39
|
end
|
@@ -13,10 +13,11 @@ module Kameleoon
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def check(data)
|
16
|
-
|
16
|
+
data ||= VisitorVisits.new([])
|
17
|
+
return false unless data.is_a?(VisitorVisits) && !@condition_value.nil?
|
17
18
|
|
18
|
-
|
19
|
-
check_targeting(
|
19
|
+
prev_visits = data.prev_visits
|
20
|
+
check_targeting(prev_visits.size + 1) # +1 for current visit
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -17,14 +17,15 @@ module Kameleoon
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def check(data)
|
20
|
-
|
20
|
+
data ||= VisitorVisits.new([])
|
21
|
+
return false unless data.is_a?(VisitorVisits)
|
21
22
|
|
22
|
-
|
23
|
+
prev_visits = data.prev_visits
|
23
24
|
case @visitor_type
|
24
25
|
when VisitorType::NEW
|
25
|
-
|
26
|
+
prev_visits.empty?
|
26
27
|
when VisitorType::RETURNING
|
27
|
-
!
|
28
|
+
!prev_visits.empty?
|
28
29
|
else
|
29
30
|
false
|
30
31
|
end
|
@@ -9,6 +9,7 @@ module Kameleoon
|
|
9
9
|
class Segment
|
10
10
|
include TreeBuilder
|
11
11
|
attr_accessor :id, :tree
|
12
|
+
|
12
13
|
def to_s
|
13
14
|
@tree.to_s
|
14
15
|
end
|
@@ -84,13 +85,13 @@ module Kameleoon
|
|
84
85
|
# Computing results
|
85
86
|
if is_left_child_targeted.nil?
|
86
87
|
if is_right_child_targeted == @or_operator
|
87
|
-
is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
|
88
|
+
is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
|
88
89
|
else
|
89
90
|
is_targeted = nil
|
90
91
|
end
|
91
92
|
else
|
92
93
|
if is_left_child_targeted == @or_operator
|
93
|
-
is_targeted = Marshal.load(Marshal.dump(@or_operator)) #Deep copy
|
94
|
+
is_targeted = Marshal.load(Marshal.dump(@or_operator)) # Deep copy
|
94
95
|
else
|
95
96
|
if is_right_child_targeted == true
|
96
97
|
is_targeted = true
|
@@ -102,7 +103,7 @@ module Kameleoon
|
|
102
103
|
end
|
103
104
|
end
|
104
105
|
end
|
105
|
-
Marshal.load(Marshal.dump(is_targeted)) #Deep copy
|
106
|
+
Marshal.load(Marshal.dump(is_targeted)) # Deep copy
|
106
107
|
end
|
107
108
|
|
108
109
|
def check_condition(datas, condition = @condition)
|
@@ -3,12 +3,12 @@
|
|
3
3
|
require 'kameleoon/logging/kameleoon_logger'
|
4
4
|
require 'kameleoon/targeting/condition'
|
5
5
|
require 'kameleoon/targeting/conditions/exclusive_experiment_condition'
|
6
|
-
require 'kameleoon/targeting/conditions/exclusive_feature_flag_condition'
|
7
6
|
require 'kameleoon/targeting/conditions/sdk_language_condition'
|
8
7
|
require 'kameleoon/targeting/conditions/segment_condition'
|
9
8
|
require 'kameleoon/targeting/conditions/target_experiment_condition'
|
10
9
|
require 'kameleoon/targeting/conditions/target_feature_flag_condition'
|
11
10
|
require 'kameleoon/targeting/conditions/target_personalization_condition'
|
11
|
+
require 'kameleoon/targeting/conditions/visit_number_today_condition'
|
12
12
|
|
13
13
|
module Kameleoon
|
14
14
|
# @api private
|
@@ -85,16 +85,15 @@ module Kameleoon
|
|
85
85
|
condition_data = TargetExperimentInfo.new(visitor&.variations)
|
86
86
|
when ConditionType::TARGET_PERSONALIZATION
|
87
87
|
condition_data = TargetPersonalizationInfo.new(visitor&.personalizations)
|
88
|
-
when ConditionType::EXCLUSIVE_FEATURE_FLAG
|
89
|
-
condition_data = ExclusiveFeatureFlagInfo.new(campaign_id, visitor&.variations)
|
90
88
|
when ConditionType::EXCLUSIVE_EXPERIMENT
|
91
89
|
condition_data = ExclusiveExperimentInfo.new(campaign_id, visitor&.variations, visitor&.personalizations)
|
92
90
|
when ConditionType::FIRST_VISIT,
|
93
91
|
ConditionType::LAST_VISIT,
|
94
92
|
ConditionType::VISITS,
|
95
|
-
ConditionType::SAME_DAY_VISITS,
|
96
93
|
ConditionType::NEW_VISITORS
|
97
94
|
condition_data = visitor.visitor_visits unless visitor.nil?
|
95
|
+
when ConditionType::SAME_DAY_VISITS
|
96
|
+
condition_data = VisitNumberTodayCondition::TargetingData.new(visitor&.time_started, visitor&.visitor_visits)
|
98
97
|
when ConditionType::HEAT_SLICE
|
99
98
|
condition_data = visitor&.kcs_heat
|
100
99
|
end
|
data/lib/kameleoon/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kameleoon-client-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kameleoon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-http-request
|
@@ -130,6 +130,7 @@ files:
|
|
130
130
|
- lib/kameleoon/network/activity_event.rb
|
131
131
|
- lib/kameleoon/network/content_type.rb
|
132
132
|
- lib/kameleoon/network/cookie/cookie_manager.rb
|
133
|
+
- lib/kameleoon/network/fetched_configuration.rb
|
133
134
|
- lib/kameleoon/network/method.rb
|
134
135
|
- lib/kameleoon/network/net_provider.rb
|
135
136
|
- lib/kameleoon/network/network_manager.rb
|
@@ -153,7 +154,6 @@ files:
|
|
153
154
|
- lib/kameleoon/targeting/conditions/custom_datum.rb
|
154
155
|
- lib/kameleoon/targeting/conditions/device_condition.rb
|
155
156
|
- lib/kameleoon/targeting/conditions/exclusive_experiment_condition.rb
|
156
|
-
- lib/kameleoon/targeting/conditions/exclusive_feature_flag_condition.rb
|
157
157
|
- lib/kameleoon/targeting/conditions/geolocation_condition.rb
|
158
158
|
- lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb
|
159
159
|
- lib/kameleoon/targeting/conditions/number_condition.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'kameleoon/targeting/condition'
|
4
|
-
|
5
|
-
module Kameleoon
|
6
|
-
# @api private
|
7
|
-
module Targeting
|
8
|
-
# ExclusiveFeatureFlag represents an instance of Exclusive FeatureFlag condition in user account
|
9
|
-
class ExclusiveFeatureFlagCondition < Condition
|
10
|
-
def check(data)
|
11
|
-
return false unless data.is_a?(ExclusiveFeatureFlagInfo)
|
12
|
-
|
13
|
-
size = data.variations_storage&.size || 0
|
14
|
-
size.zero? || (size == 1 && !data.variations_storage.get(data.current_experiment_id).nil?)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class ExclusiveFeatureFlagInfo
|
19
|
-
attr_reader :current_experiment_id, :variations_storage
|
20
|
-
|
21
|
-
def initialize(current_experiment_id, variations_storage)
|
22
|
-
@current_experiment_id = current_experiment_id
|
23
|
-
@variations_storage = variations_storage
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|