kameleoon-client-ruby 3.19.0 → 3.20.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/data/browser.rb +1 -1
- data/lib/kameleoon/data/conversion.rb +12 -3
- data/lib/kameleoon/data/custom_data.rb +1 -1
- data/lib/kameleoon/data/data.rb +1 -1
- data/lib/kameleoon/data/device.rb +1 -1
- data/lib/kameleoon/data/geolocation.rb +1 -1
- data/lib/kameleoon/data/manager/assigned_variation.rb +2 -2
- data/lib/kameleoon/data/manager/page_view_visit.rb +2 -2
- data/lib/kameleoon/data/manager/visitor.rb +2 -2
- data/lib/kameleoon/data/manager/visitor_manager.rb +1 -1
- data/lib/kameleoon/data/mapping_identifier.rb +1 -1
- data/lib/kameleoon/data/operating_system.rb +1 -2
- data/lib/kameleoon/data/page_view.rb +1 -1
- data/lib/kameleoon/data/personalization.rb +4 -3
- data/lib/kameleoon/data/targeted_segment.rb +1 -1
- data/lib/kameleoon/data/visitor_visits.rb +1 -1
- data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +20 -5
- data/lib/kameleoon/managers/tracking/tracking_builder.rb +1 -1
- data/lib/kameleoon/network/access_token_source.rb +2 -2
- data/lib/kameleoon/network/activity_event.rb +1 -1
- data/lib/kameleoon/targeting/conditions/conversion_condition.rb +28 -13
- data/lib/kameleoon/targeting/conditions/exclusive_experiment_condition.rb +38 -19
- data/lib/kameleoon/targeting/conditions/target_experiment_condition.rb +12 -10
- data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +26 -22
- data/lib/kameleoon/targeting/conditions/target_personalization_condition.rb +12 -10
- data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +1 -2
- data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +2 -2
- data/lib/kameleoon/targeting/conditions/visitor_scope_condition.rb +56 -0
- data/lib/kameleoon/targeting/targeting_manager.rb +12 -5
- data/lib/kameleoon/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6e939e54b9b0f43a46d27b3ce7d1d442916d036a7db9ca5159645ffc869415b
|
|
4
|
+
data.tar.gz: 98b6e2c2b04c183ba5052379066e3eff95c9ffdc2b28963a0bd106dcf8adb0db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c4eb181ded7a566731df5e6af6bcb271fcf852c27503b5e0c44be51eb93b729decb88c378732f9a9584d5d1ef452e71a5ba651a39d20a06f7e689a3b016bc65a
|
|
7
|
+
data.tar.gz: 997194371128c0c25cd394ab7ed22e05a0b57684915c96268874ab9e426aa3e0228f396488e77da61c2b122798eeccfee2ad1e8785900ddff24452faac953025
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'set'
|
|
4
5
|
require 'kameleoon/data/custom_data'
|
|
5
6
|
require 'kameleoon/network/uri_helper'
|
|
6
7
|
require 'kameleoon/utils'
|
|
@@ -9,11 +10,11 @@ require_relative 'data'
|
|
|
9
10
|
module Kameleoon
|
|
10
11
|
# Conversion class uses for tracking conversion
|
|
11
12
|
class Conversion < DuplicationSafeData
|
|
12
|
-
attr_reader :goal_id, :revenue, :negative, :metadata
|
|
13
|
+
attr_reader :goal_id, :revenue, :negative, :metadata, :assignment_time
|
|
13
14
|
|
|
14
15
|
def to_s
|
|
15
16
|
"Conversion{goal_id:#{@goal_id},revenue:#{@revenue},negative:#{@negative}," \
|
|
16
|
-
"metadata:#{Utils::Strval.obj_to_s(@metadata)}}"
|
|
17
|
+
"metadata:#{Utils::Strval.obj_to_s(@metadata)},assignment_time:#{@assignment_time}}"
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# @param [Integer] goal_id Id of the goal associated to the conversion
|
|
@@ -26,9 +27,17 @@ module Kameleoon
|
|
|
26
27
|
@revenue = revenue || 0.0
|
|
27
28
|
@negative = negative || false
|
|
28
29
|
@metadata = metadata
|
|
30
|
+
@assignment_time = Time.now.to_f
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
# @api private
|
|
34
|
+
def self.build_internal(goal_id, revenue = 0.0, negative = false, metadata: nil, assignment_time: nil)
|
|
35
|
+
conversion = new(goal_id, revenue, negative, metadata: metadata)
|
|
36
|
+
conversion.instance_variable_set(:@assignment_time, assignment_time || Time.now.to_f)
|
|
37
|
+
conversion
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def query
|
|
32
41
|
params = {
|
|
33
42
|
eventType: 'conversion',
|
|
34
43
|
goalId: @goal_id,
|
data/lib/kameleoon/data/data.rb
CHANGED
|
@@ -25,10 +25,10 @@ module Kameleoon
|
|
|
25
25
|
@experiment_id = experiment_id
|
|
26
26
|
@variation_id = variation_id
|
|
27
27
|
@rule_type = rule_type
|
|
28
|
-
@assignment_time = assignment_time || Time.
|
|
28
|
+
@assignment_time = assignment_time || Time.now.to_f
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def query
|
|
32
32
|
params = {
|
|
33
33
|
eventType: EVENT_TYPE,
|
|
34
34
|
id: @experiment_id,
|
|
@@ -12,7 +12,7 @@ module Kameleoon
|
|
|
12
12
|
def initialize(page_view, count = 1, timestamp = nil)
|
|
13
13
|
@page_view = page_view
|
|
14
14
|
@count = count
|
|
15
|
-
@last_timestamp = timestamp.nil? ? Time.new.
|
|
15
|
+
@last_timestamp = timestamp.nil? ? Time.new.to_f : timestamp
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Not thread-save method, should be called in synchronized code
|
|
@@ -20,7 +20,7 @@ module Kameleoon
|
|
|
20
20
|
def overwrite(page_view)
|
|
21
21
|
@page_view = page_view
|
|
22
22
|
@count += 1
|
|
23
|
-
@last_timestamp = Time.new.
|
|
23
|
+
@last_timestamp = Time.new.to_f
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Not thread-save method, should be called in synchronized code
|
|
@@ -55,7 +55,7 @@ module Kameleoon
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def update_last_activity_time
|
|
58
|
-
@data.last_activity_time = Time.new.
|
|
58
|
+
@data.last_activity_time = Time.new.to_f
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def enumerate_sendable_data(&blk)
|
|
@@ -322,7 +322,7 @@ module Kameleoon
|
|
|
322
322
|
|
|
323
323
|
def initialize
|
|
324
324
|
Logging::KameleoonLogger.debug('CALL: VisitorData.new')
|
|
325
|
-
@time_started =
|
|
325
|
+
@time_started = Time.now.to_f
|
|
326
326
|
@mutex = Concurrent::ReadWriteLock.new
|
|
327
327
|
@legal_consent = LegalConsent::UNKNOWN
|
|
328
328
|
Logging::KameleoonLogger.debug('RETURN: VisitorData.new')
|
|
@@ -154,7 +154,7 @@ module Kameleoon
|
|
|
154
154
|
|
|
155
155
|
def purge
|
|
156
156
|
Logging::KameleoonLogger.debug('CALL: VisitorManager.purge')
|
|
157
|
-
expired_time = Time.new.
|
|
157
|
+
expired_time = Time.new.to_f - @expiration_period
|
|
158
158
|
@visitors.each_pair do |vc, v|
|
|
159
159
|
next if v.last_activity_time >= expired_time
|
|
160
160
|
|
|
@@ -62,7 +62,7 @@ module Kameleoon
|
|
|
62
62
|
@os_type = os_type
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def query
|
|
66
66
|
params = {
|
|
67
67
|
eventType: 'staticData',
|
|
68
68
|
os: OperatingSystemType.name_from_type(@os_type),
|
|
@@ -73,4 +73,3 @@ module Kameleoon
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
|
-
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Kameleoon
|
|
4
4
|
class Personalization
|
|
5
|
-
attr_reader :id, :variation_id
|
|
5
|
+
attr_reader :id, :variation_id, :assignment_time
|
|
6
6
|
|
|
7
|
-
def initialize(id, variation_id)
|
|
7
|
+
def initialize(id, variation_id, assignment_time: nil)
|
|
8
8
|
@id = id
|
|
9
9
|
@variation_id = variation_id
|
|
10
|
+
@assignment_time = assignment_time || Time.now.to_f
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def to_s
|
|
13
|
-
"Personalization{id:#{@id},variation_id:#{@variation_id}}"
|
|
14
|
+
"Personalization{id:#{@id},variation_id:#{@variation_id},assignment_time:#{@assignment_time}}"
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
@@ -32,7 +32,9 @@ module Kameleoon
|
|
|
32
32
|
prev_visits = []
|
|
33
33
|
previous_visits.each_index do |i|
|
|
34
34
|
visit = previous_visits[i]
|
|
35
|
-
prev_visits.push(
|
|
35
|
+
prev_visits.push(
|
|
36
|
+
VisitorVisits::Visit.new(ms_to_s(visit['timeStarted'] || 0), ms_to_s(visit['timeLastEvent']))
|
|
37
|
+
)
|
|
36
38
|
parse_visit(visit, i + 1)
|
|
37
39
|
end
|
|
38
40
|
@visitor_visits = VisitorVisits.new(prev_visits, @visit_number)
|
|
@@ -113,7 +115,7 @@ module Kameleoon
|
|
|
113
115
|
page_view_visit = @page_view_visits[href]
|
|
114
116
|
if page_view_visit.nil?
|
|
115
117
|
page_view = PageView.new(href, page_event['data']['title'])
|
|
116
|
-
@page_view_visits[href] = DataManager::PageViewVisit.new(page_view, 1, page_event['time'])
|
|
118
|
+
@page_view_visits[href] = DataManager::PageViewVisit.new(page_view, 1, ms_to_s(page_event['time']))
|
|
117
119
|
else
|
|
118
120
|
page_view_visit.increase_page_visits
|
|
119
121
|
end
|
|
@@ -128,7 +130,7 @@ module Kameleoon
|
|
|
128
130
|
|
|
129
131
|
@experiments[id] = DataManager::AssignedVariation.new(
|
|
130
132
|
id, experiment_event['data']['variationId'],
|
|
131
|
-
Configuration::RuleType::UNKNOWN, assignment_time: experiment_event['time']
|
|
133
|
+
Configuration::RuleType::UNKNOWN, assignment_time: ms_to_s(experiment_event['time'])
|
|
132
134
|
)
|
|
133
135
|
end
|
|
134
136
|
end
|
|
@@ -137,7 +139,10 @@ module Kameleoon
|
|
|
137
139
|
@conversions = [] if @conversions.nil?
|
|
138
140
|
conversion_events.each do |conversion_event|
|
|
139
141
|
data = conversion_event['data']
|
|
140
|
-
conversion = Conversion.
|
|
142
|
+
conversion = Conversion.send(
|
|
143
|
+
:build_internal, data['goalId'], data['revenue'], data['negative'],
|
|
144
|
+
assignment_time: ms_to_s(conversion_event['time'])
|
|
145
|
+
)
|
|
141
146
|
@conversions.push(conversion)
|
|
142
147
|
end
|
|
143
148
|
end
|
|
@@ -173,10 +178,20 @@ module Kameleoon
|
|
|
173
178
|
id = personalization_event['data']['id']
|
|
174
179
|
next if @personalizations.include?(id)
|
|
175
180
|
|
|
176
|
-
@personalizations[id] = Personalization.new(
|
|
181
|
+
@personalizations[id] = Personalization.new(
|
|
182
|
+
id,
|
|
183
|
+
personalization_event['data']['variationId'],
|
|
184
|
+
assignment_time: ms_to_s(personalization_event['time'])
|
|
185
|
+
)
|
|
177
186
|
end
|
|
178
187
|
end
|
|
179
188
|
|
|
189
|
+
def ms_to_s(timestamp)
|
|
190
|
+
return nil if timestamp.nil?
|
|
191
|
+
|
|
192
|
+
timestamp / 1000.0
|
|
193
|
+
end
|
|
194
|
+
|
|
180
195
|
def conversions_single_objects
|
|
181
196
|
objects = []
|
|
182
197
|
objects += @conversions unless @conversions.nil?
|
|
@@ -39,7 +39,7 @@ module Kameleoon
|
|
|
39
39
|
|
|
40
40
|
def get_token(timeout = nil)
|
|
41
41
|
Logging::KameleoonLogger.debug('CALL: AccessTokenSource.getToken(timeout: %s)', timeout)
|
|
42
|
-
now = Time.new.
|
|
42
|
+
now = Time.new.to_f
|
|
43
43
|
token = @cached_token
|
|
44
44
|
return call_fetch_token(timeout) if token.nil? || token.expired?(now)
|
|
45
45
|
|
|
@@ -105,7 +105,7 @@ module Kameleoon
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def handle_fetched_token(token, expires_in)
|
|
108
|
-
now = Time.new.
|
|
108
|
+
now = Time.new.to_f
|
|
109
109
|
exp_time = now + expires_in - TOKEN_EXPIRATION_GAP
|
|
110
110
|
if expires_in > TOKEN_OBSOLESCENCE_GAP
|
|
111
111
|
obs_time = now + expires_in - TOKEN_OBSOLESCENCE_GAP
|
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/data/conversion'
|
|
4
|
-
require 'kameleoon/
|
|
4
|
+
require 'kameleoon/targeting/conditions/visitor_scope_condition'
|
|
5
5
|
|
|
6
6
|
module Kameleoon
|
|
7
|
-
# @api private
|
|
8
7
|
module Targeting
|
|
9
|
-
|
|
10
|
-
class ConversionCondition < Condition
|
|
8
|
+
class ConversionCondition < VisitorScopeCondition
|
|
11
9
|
def initialize(json_condition)
|
|
12
|
-
super(json_condition)
|
|
10
|
+
super(json_condition, VisitScope::VISITOR)
|
|
13
11
|
@goal_id = json_condition['goalId']
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
def check(
|
|
17
|
-
return false unless
|
|
18
|
-
return true if @goal_id.nil?
|
|
14
|
+
def check(data)
|
|
15
|
+
return false unless data.is_a?(TargetingData)
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
threshold = assignment_threshold(data.visitor_visits)
|
|
18
|
+
targeted_conversion?(data.conversions, threshold)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def targeted_conversion?(conversions, threshold)
|
|
24
|
+
return false unless conversions.respond_to?(:enumerate)
|
|
25
|
+
|
|
26
|
+
targeted = false
|
|
27
|
+
conversions.enumerate do |conversion|
|
|
28
|
+
targeted = (@goal_id.nil? || @goal_id == conversion.goal_id) && conversion.assignment_time >= threshold
|
|
29
|
+
break if targeted
|
|
30
|
+
end
|
|
31
|
+
targeted
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class TargetingData
|
|
35
|
+
attr_reader :conversions, :visitor_visits
|
|
36
|
+
|
|
37
|
+
def initialize(conversions, visitor_visits)
|
|
38
|
+
@conversions = conversions
|
|
39
|
+
@visitor_visits = visitor_visits
|
|
24
40
|
end
|
|
25
|
-
is_targeted
|
|
26
41
|
end
|
|
27
42
|
end
|
|
28
43
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/logging/kameleoon_logger'
|
|
4
|
-
require 'kameleoon/targeting/
|
|
4
|
+
require 'kameleoon/targeting/conditions/visitor_scope_condition'
|
|
5
5
|
|
|
6
6
|
module Kameleoon
|
|
7
7
|
# @api private
|
|
8
8
|
module Targeting
|
|
9
9
|
# ExclusiveExperiment represents an instance of Exclusive Experiment condition in user account
|
|
10
|
-
class ExclusiveExperimentCondition <
|
|
10
|
+
class ExclusiveExperimentCondition < VisitorScopeCondition
|
|
11
11
|
module CampaignType
|
|
12
12
|
EXPERIMENT = 'EXPERIMENT'
|
|
13
13
|
PERSONALIZATION = 'PERSONALIZATION'
|
|
@@ -15,21 +15,22 @@ module Kameleoon
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def initialize(json_condition)
|
|
18
|
-
super(json_condition)
|
|
18
|
+
super(json_condition, VisitScope::VISITOR)
|
|
19
19
|
|
|
20
20
|
@campaign_type = json_condition['campaignType']
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def check(data)
|
|
24
|
-
return false unless data.is_a?(
|
|
24
|
+
return false unless data.is_a?(TargetingData)
|
|
25
25
|
|
|
26
|
+
threshold = assignment_threshold(data.visitor_visits)
|
|
26
27
|
case @campaign_type
|
|
27
28
|
when CampaignType::EXPERIMENT
|
|
28
|
-
return check_experiment(data)
|
|
29
|
+
return check_experiment(data, threshold)
|
|
29
30
|
when CampaignType::PERSONALIZATION
|
|
30
|
-
return check_personalization(data)
|
|
31
|
+
return check_personalization(data, threshold)
|
|
31
32
|
when CampaignType::ANY
|
|
32
|
-
return check_personalization(data) && check_experiment(data)
|
|
33
|
+
return check_personalization(data, threshold) && check_experiment(data, threshold)
|
|
33
34
|
end
|
|
34
35
|
Logging::KameleoonLogger.error("Unexpected campaign type for '#{type}' condition: '#{@campaign_type}'")
|
|
35
36
|
false
|
|
@@ -37,23 +38,41 @@ module Kameleoon
|
|
|
37
38
|
|
|
38
39
|
private
|
|
39
40
|
|
|
40
|
-
def check_experiment(data)
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
def check_experiment(data, threshold)
|
|
42
|
+
return true if data.variations.nil?
|
|
43
|
+
|
|
44
|
+
result = true
|
|
45
|
+
data.variations.enumerate do |variation|
|
|
46
|
+
if variation.experiment_id != data.current_experiment_id && variation.assignment_time >= threshold
|
|
47
|
+
result = false
|
|
48
|
+
break
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
result
|
|
43
52
|
end
|
|
44
53
|
|
|
45
|
-
def check_personalization(data)
|
|
46
|
-
|
|
54
|
+
def check_personalization(data, threshold)
|
|
55
|
+
return true if data.personalizations.nil?
|
|
56
|
+
|
|
57
|
+
result = true
|
|
58
|
+
data.personalizations.enumerate do |personalization|
|
|
59
|
+
if personalization.assignment_time >= threshold
|
|
60
|
+
result = false
|
|
61
|
+
break
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
result
|
|
47
65
|
end
|
|
48
|
-
end
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
class TargetingData
|
|
68
|
+
attr_reader :current_experiment_id, :variations, :personalizations, :visitor_visits
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
def initialize(current_experiment_id, variations, personalizations, visitor_visits = nil)
|
|
71
|
+
@current_experiment_id = current_experiment_id
|
|
72
|
+
@variations = variations
|
|
73
|
+
@personalizations = personalizations
|
|
74
|
+
@visitor_visits = visitor_visits
|
|
75
|
+
end
|
|
57
76
|
end
|
|
58
77
|
end
|
|
59
78
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/logging/kameleoon_logger'
|
|
4
|
-
require 'kameleoon/targeting/
|
|
4
|
+
require 'kameleoon/targeting/conditions/visitor_scope_condition'
|
|
5
5
|
|
|
6
6
|
module Kameleoon
|
|
7
7
|
# @api private
|
|
8
8
|
module Targeting
|
|
9
9
|
# TargetExperiment represents an instance of Experiment condition in user account
|
|
10
|
-
class TargetExperimentCondition <
|
|
10
|
+
class TargetExperimentCondition < VisitorScopeCondition
|
|
11
11
|
include Kameleoon::Exception
|
|
12
12
|
|
|
13
13
|
def initialize(json_condition)
|
|
14
|
-
super(json_condition)
|
|
14
|
+
super(json_condition, VisitScope::CURRENT_VISIT)
|
|
15
15
|
|
|
16
16
|
@variation_id = json_condition['variationId'] || -1
|
|
17
17
|
@experiment_id = json_condition['experimentId'] || -1
|
|
@@ -19,9 +19,10 @@ module Kameleoon
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def check(data)
|
|
22
|
-
return false unless data.is_a?(
|
|
22
|
+
return false unless data.is_a?(TargetingData)
|
|
23
23
|
|
|
24
|
-
variation = data.
|
|
24
|
+
variation = data.variations&.get(@experiment_id)
|
|
25
|
+
variation = nil if !variation.nil? && variation.assignment_time < assignment_threshold(data.visitor_visits)
|
|
25
26
|
case @variation_match_type
|
|
26
27
|
when Operator::ANY
|
|
27
28
|
return !variation.nil?
|
|
@@ -33,13 +34,14 @@ module Kameleoon
|
|
|
33
34
|
)
|
|
34
35
|
false
|
|
35
36
|
end
|
|
36
|
-
end
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
class TargetingData
|
|
39
|
+
attr_reader :variations, :visitor_visits
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
def initialize(variations, visitor_visits = nil)
|
|
42
|
+
@variations = variations
|
|
43
|
+
@visitor_visits = visitor_visits
|
|
44
|
+
end
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
end
|
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'kameleoon/targeting/
|
|
3
|
+
require 'kameleoon/targeting/conditions/visitor_scope_condition'
|
|
4
4
|
require 'kameleoon/exceptions'
|
|
5
5
|
|
|
6
6
|
module Kameleoon
|
|
7
7
|
# @api private
|
|
8
8
|
module Targeting
|
|
9
|
-
|
|
10
|
-
class TargetFeatureFlagCondition < Condition
|
|
9
|
+
class TargetFeatureFlagCondition < VisitorScopeCondition
|
|
11
10
|
include Kameleoon::Exception
|
|
12
11
|
|
|
13
12
|
def initialize(json_condition)
|
|
14
|
-
super(json_condition)
|
|
15
|
-
|
|
13
|
+
super(json_condition, VisitScope::CURRENT_VISIT)
|
|
16
14
|
@feature_flag_id = json_condition['featureFlagId']
|
|
17
15
|
@condition_variation_key = json_condition['variationKey']
|
|
18
16
|
@condition_rule_id = json_condition['ruleId']
|
|
19
17
|
end
|
|
20
18
|
|
|
21
19
|
def check(data)
|
|
22
|
-
return false unless
|
|
20
|
+
return false unless valid_data?(data)
|
|
23
21
|
|
|
24
22
|
get_rules(data).any? { |rule| check_rule(data, rule) }
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
private
|
|
28
26
|
|
|
27
|
+
def valid_data?(data)
|
|
28
|
+
data.is_a?(TargetingData) && !data.data_file.nil? && data.variations&.size.to_i.positive?
|
|
29
|
+
end
|
|
30
|
+
|
|
29
31
|
def check_rule(data, rule)
|
|
30
32
|
return false unless rule.is_a?(Configuration::Rule)
|
|
31
|
-
return false if
|
|
32
|
-
|
|
33
|
-
variation = data.variations_storage.get(rule.experiment.id)
|
|
34
|
-
return false if variation.nil?
|
|
33
|
+
return false if @condition_rule_id && @condition_rule_id != rule.id
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
variation = data.variations.get(rule.experiment.id)
|
|
36
|
+
return false if variation.nil? || variation.assignment_time < assignment_threshold(data.visitor_visits)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
check_variation_key(data.data_file, variation)
|
|
39
|
+
end
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
def check_variation_key(data_file, variation)
|
|
42
|
+
return true if @condition_variation_key.nil?
|
|
41
43
|
|
|
42
|
-
variation.
|
|
44
|
+
resolved = data_file.variation_by_id[variation.variation_id]
|
|
45
|
+
resolved.is_a?(Configuration::VariationByExposition) &&
|
|
46
|
+
resolved.variation_key == @condition_variation_key
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def get_rules(data)
|
|
46
|
-
|
|
47
|
-
feature_flag&.rules || []
|
|
50
|
+
data.data_file.feature_flag_by_id[@feature_flag_id]&.rules || []
|
|
48
51
|
end
|
|
49
|
-
end
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
class TargetingData
|
|
54
|
+
attr_reader :data_file, :variations, :visitor_visits
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
def initialize(data_file, variations, visitor_visits = nil)
|
|
57
|
+
@data_file = data_file
|
|
58
|
+
@variations = variations
|
|
59
|
+
@visitor_visits = visitor_visits
|
|
60
|
+
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
end
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'kameleoon/targeting/
|
|
3
|
+
require 'kameleoon/targeting/conditions/visitor_scope_condition'
|
|
4
4
|
|
|
5
5
|
module Kameleoon
|
|
6
6
|
# @api private
|
|
7
7
|
module Targeting
|
|
8
8
|
# TargetPersonalization represents an instance of Personalization condition in user account
|
|
9
|
-
class TargetPersonalizationCondition <
|
|
9
|
+
class TargetPersonalizationCondition < VisitorScopeCondition
|
|
10
10
|
include Kameleoon::Exception
|
|
11
11
|
|
|
12
12
|
def initialize(json_condition)
|
|
13
|
-
super(json_condition)
|
|
13
|
+
super(json_condition, VisitScope::CURRENT_VISIT)
|
|
14
14
|
|
|
15
15
|
@personalization_id = json_condition['personalizationId'] || -1
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def check(data)
|
|
19
|
-
return false unless data.is_a?(
|
|
19
|
+
return false unless data.is_a?(TargetingData)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
personalization = data.personalizations&.get(@personalization_id)
|
|
22
|
+
!personalization.nil? && personalization.assignment_time >= assignment_threshold(data.visitor_visits)
|
|
22
23
|
end
|
|
23
|
-
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
class TargetingData
|
|
26
|
+
attr_reader :personalizations, :visitor_visits
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def initialize(personalizations, visitor_visits = nil)
|
|
29
|
+
@personalizations = personalizations
|
|
30
|
+
@visitor_visits = visitor_visits
|
|
31
|
+
end
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|
|
@@ -19,9 +19,8 @@ module Kameleoon
|
|
|
19
19
|
|
|
20
20
|
prev_visits = data.prev_visits
|
|
21
21
|
if prev_visits.size >= 1
|
|
22
|
-
now = (Time.now.to_f * 1000).to_i # ... * 1000 for convert seconds to milliseconds
|
|
23
22
|
visit_time = prev_visits[@is_first_visit ? prev_visits.size - 1 : 0].time_started
|
|
24
|
-
return check_targeting(now - visit_time)
|
|
23
|
+
return check_targeting(Time.now.to_f - visit_time)
|
|
25
24
|
end
|
|
26
25
|
false
|
|
27
26
|
end
|
|
@@ -16,7 +16,7 @@ module Kameleoon
|
|
|
16
16
|
return false unless data.is_a?(TargetingData) && !@condition_value.nil?
|
|
17
17
|
|
|
18
18
|
number_of_visits_today = 0
|
|
19
|
-
start_of_day =
|
|
19
|
+
start_of_day = Time.new.to_date.to_time.to_f
|
|
20
20
|
data.visitor_visits.prev_visits.each do |visit|
|
|
21
21
|
break if visit.time_started < start_of_day
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ module Kameleoon
|
|
|
31
31
|
|
|
32
32
|
def initialize(current_visit_time_started, visitor_visits)
|
|
33
33
|
@current_visit_time_started =
|
|
34
|
-
current_visit_time_started.is_a?(Integer) ? current_visit_time_started :
|
|
34
|
+
current_visit_time_started.is_a?(Integer) ? current_visit_time_started : Time.new.to_f
|
|
35
35
|
@visitor_visits = visitor_visits.is_a?(VisitorVisits) ? visitor_visits : VisitorVisits.new([])
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kameleoon/data/visitor_visits'
|
|
4
|
+
require 'kameleoon/targeting/condition'
|
|
5
|
+
|
|
6
|
+
module Kameleoon
|
|
7
|
+
# @api private
|
|
8
|
+
module Targeting
|
|
9
|
+
module VisitScope
|
|
10
|
+
CURRENT_VISIT = 'CURRENT_VISIT'
|
|
11
|
+
VISITOR = 'VISITOR'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class VisitorScopeCondition < Condition
|
|
15
|
+
MIN_VISITOR_VISIT_COUNT = 2
|
|
16
|
+
MAX_VISITOR_VISIT_COUNT = 25
|
|
17
|
+
|
|
18
|
+
def initialize(json_condition, default_visit_scope)
|
|
19
|
+
super(json_condition)
|
|
20
|
+
@visit_scope = parse_visit_scope(json_condition['visitScope'], default_visit_scope)
|
|
21
|
+
@visit_count = parse_visit_count(json_condition['visitCount'])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def assignment_threshold(visitor_visits)
|
|
27
|
+
return 0.0 unless visitor_visits.is_a?(Kameleoon::VisitorVisits)
|
|
28
|
+
|
|
29
|
+
prev_visits = visitor_visits.prev_visits
|
|
30
|
+
if @visit_scope == VisitScope::CURRENT_VISIT || @visit_count < MIN_VISITOR_VISIT_COUNT || prev_visits.empty?
|
|
31
|
+
return visitor_visits.time_started
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
visit_index = [[@visit_count - MIN_VISITOR_VISIT_COUNT, 0].max, prev_visits.size - 1].min
|
|
35
|
+
prev_visits[visit_index].time_started
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_visit_scope(value, default_visit_scope)
|
|
39
|
+
case value&.upcase
|
|
40
|
+
when VisitScope::CURRENT_VISIT
|
|
41
|
+
VisitScope::CURRENT_VISIT
|
|
42
|
+
when VisitScope::VISITOR
|
|
43
|
+
VisitScope::VISITOR
|
|
44
|
+
else
|
|
45
|
+
default_visit_scope
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_visit_count(value)
|
|
50
|
+
return value if value.is_a?(Integer) && value.positive?
|
|
51
|
+
|
|
52
|
+
MAX_VISITOR_VISIT_COUNT
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'kameleoon/logging/kameleoon_logger'
|
|
4
4
|
require 'kameleoon/targeting/condition'
|
|
5
|
+
require 'kameleoon/targeting/conditions/conversion_condition'
|
|
5
6
|
require 'kameleoon/targeting/conditions/exclusive_experiment_condition'
|
|
6
7
|
require 'kameleoon/targeting/conditions/sdk_language_condition'
|
|
7
8
|
require 'kameleoon/targeting/conditions/segment_condition'
|
|
@@ -68,20 +69,26 @@ module Kameleoon
|
|
|
68
69
|
}
|
|
69
70
|
)
|
|
70
71
|
when ConditionType::CONVERSIONS
|
|
71
|
-
condition_data = visitor
|
|
72
|
+
condition_data = ConversionCondition::TargetingData.new(visitor&.conversions, visitor&.visitor_visits)
|
|
72
73
|
when ConditionType::SDK_LANGUAGE
|
|
73
74
|
condition_data = SdkInfo.new(Kameleoon::SDK_NAME, Kameleoon::SDK_VERSION)
|
|
74
75
|
when ConditionType::VISITOR_CODE
|
|
75
76
|
condition_data = visitor_code
|
|
76
77
|
when ConditionType::TARGET_FEATURE_FLAG
|
|
77
|
-
condition_data =
|
|
78
|
+
condition_data = TargetFeatureFlagCondition::TargetingData.new(
|
|
79
|
+
@data_manager.data_file, visitor&.variations, visitor&.visitor_visits
|
|
80
|
+
)
|
|
78
81
|
when ConditionType::TARGET_EXPERIMENT
|
|
79
|
-
condition_data =
|
|
82
|
+
condition_data = TargetExperimentCondition::TargetingData.new(visitor&.variations, visitor&.visitor_visits)
|
|
80
83
|
when ConditionType::TARGET_PERSONALIZATION
|
|
81
|
-
condition_data =
|
|
84
|
+
condition_data = TargetPersonalizationCondition::TargetingData.new(
|
|
85
|
+
visitor&.personalizations, visitor&.visitor_visits
|
|
86
|
+
)
|
|
82
87
|
when ConditionType::EXCLUSIVE_EXPERIMENT
|
|
83
88
|
unless campaign_id.nil?
|
|
84
|
-
condition_data =
|
|
89
|
+
condition_data = ExclusiveExperimentCondition::TargetingData.new(
|
|
90
|
+
campaign_id, visitor&.variations, visitor&.personalizations, visitor&.visitor_visits
|
|
91
|
+
)
|
|
85
92
|
end
|
|
86
93
|
when ConditionType::FIRST_VISIT,
|
|
87
94
|
ConditionType::LAST_VISIT,
|
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.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kameleoon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: em-http-request
|
|
@@ -178,6 +178,7 @@ files:
|
|
|
178
178
|
- lib/kameleoon/targeting/conditions/visit_number_total_condition.rb
|
|
179
179
|
- lib/kameleoon/targeting/conditions/visitor_code_condition.rb
|
|
180
180
|
- lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb
|
|
181
|
+
- lib/kameleoon/targeting/conditions/visitor_scope_condition.rb
|
|
181
182
|
- lib/kameleoon/targeting/models.rb
|
|
182
183
|
- lib/kameleoon/targeting/targeting_manager.rb
|
|
183
184
|
- lib/kameleoon/targeting/tree_builder.rb
|