kameleoon-client-ruby 3.12.0 → 3.13.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 +13 -5
- data/lib/kameleoon/data/data.rb +1 -0
- data/lib/kameleoon/data/device.rb +1 -1
- data/lib/kameleoon/data/manager/visitor.rb +17 -5
- data/lib/kameleoon/data/visitor_visits.rb +64 -11
- data/lib/kameleoon/kameleoon_client.rb +10 -7
- 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/managers/tracking/tracking_builder.rb +4 -4
- 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 +24 -10
- data/lib/kameleoon/network/request.rb +1 -1
- data/lib/kameleoon/network/response.rb +5 -4
- data/lib/kameleoon/network/url_provider.rb +1 -1
- 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/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: 05500f77891f22fd67b10f0dec830e6673a77d8ff83b391ffbf8547893bfcf1e
|
4
|
+
data.tar.gz: 4c572f1464af6e5026642e4fac03dcc1e1a99715228c76982c67de5063c2235e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7ac1acb43bf56661f194dacc810b064e2c4cba67010b1af49d928c190ae53c139ee0447fb7e5f5670ae1fb0c406a0efb7e2c7fe0eedac323635dc0e215de869
|
7
|
+
data.tar.gz: 58278744be7aaef43a0e0dcbe71bf93ee9ab9c1aec3a0b6e6f296e940f24ce943ee7c7998a550ecf1950cbcb3f36cb78858feacea6289a2b8ce1c3439b4b037e
|
@@ -10,28 +10,36 @@ require 'kameleoon/logging/kameleoon_logger'
|
|
10
10
|
module Kameleoon
|
11
11
|
module Configuration
|
12
12
|
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,
|
13
|
+
attr_reader :last_modified, :settings, :feature_flags, :me_groups, :has_any_targeted_delivery_rule,
|
14
|
+
:feature_flag_by_id, :rule_by_segment_id, :rule_info_by_exp_id, :variation_by_id, :custom_data_info,
|
15
15
|
:experiment_ids_with_js_css_variable, :holdout
|
16
16
|
|
17
17
|
def to_s
|
18
18
|
'DataFile{' \
|
19
19
|
"environment:#{@environment}," \
|
20
|
+
"last_modified:#{@last_modified}," \
|
20
21
|
"feature_flags:#{@feature_flags.size}," \
|
21
22
|
"settings:#{@settings}" \
|
22
23
|
'}'
|
23
24
|
end
|
24
25
|
|
25
|
-
def initialize(environment, configuration = nil)
|
26
|
-
Logging::KameleoonLogger.debug(
|
26
|
+
def initialize(environment, configuration = nil, last_modified = nil)
|
27
|
+
Logging::KameleoonLogger.debug(
|
28
|
+
"CALL: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
|
29
|
+
environment, last_modified
|
30
|
+
)
|
27
31
|
@environment = environment
|
32
|
+
@last_modified = last_modified
|
28
33
|
if configuration.nil?
|
29
34
|
init_default
|
30
35
|
else
|
31
36
|
init(configuration)
|
32
37
|
end
|
33
38
|
collect_indices
|
34
|
-
Logging::KameleoonLogger.debug(
|
39
|
+
Logging::KameleoonLogger.debug(
|
40
|
+
"RETURN: DataFile.new(environment: '%s', configuration, last_modified: '%s')",
|
41
|
+
environment, last_modified
|
42
|
+
)
|
35
43
|
end
|
36
44
|
|
37
45
|
def get_feature_flag(feature_key)
|
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,11 +324,12 @@ 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) }
|
324
|
-
@conversions&.each { |c| blk.call(c) }
|
325
331
|
@variations&.each { |_, av| blk.call(av) }
|
332
|
+
@conversions&.each { |c| blk.call(c) }
|
326
333
|
end
|
327
334
|
end
|
328
335
|
|
@@ -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)
|
@@ -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
|
@@ -103,16 +103,16 @@ module Kameleoon
|
|
103
103
|
next true
|
104
104
|
end
|
105
105
|
else
|
106
|
-
visitor.conversions.enumerate do |c|
|
107
|
-
unsent_data.push(c) if c.unsent
|
108
|
-
next true
|
109
|
-
end
|
110
106
|
if @data_file.has_any_targeted_delivery_rule
|
111
107
|
visitor.variations.enumerate do |av|
|
112
108
|
unsent_data.push(av) if av.unsent && (av.rule_type == Configuration::RuleType::TARGETED_DELIVERY)
|
113
109
|
next true
|
114
110
|
end
|
115
111
|
end
|
112
|
+
visitor.conversions.enumerate do |c|
|
113
|
+
unsent_data.push(c) if c.unsent
|
114
|
+
next true
|
115
|
+
end
|
116
116
|
end
|
117
117
|
end
|
118
118
|
unsent_data.push(Network::ActivityEvent.new) if unsent_data.empty? && is_consent_given
|
@@ -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)
|
@@ -58,7 +67,7 @@ module Kameleoon
|
|
58
67
|
url = @url_provider.make_tracking_url
|
59
68
|
timeout = ensure_timeout(timeout)
|
60
69
|
request = Request.new(Method::POST, url, ContentType::TEXT, timeout, data: lines)
|
61
|
-
make_call(request, true, TRACKING_CALL_ATTEMPT_NUMBER - 1, TRACKING_CALL_RETRY_DELAY)
|
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
|
@@ -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
|
@@ -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.13.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-05-26 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
|