kameleoon-client-ruby 1.1.2 → 2.1.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/client.rb +541 -404
- data/lib/kameleoon/configuration/experiment.rb +42 -0
- data/lib/kameleoon/configuration/feature_flag.rb +30 -0
- data/lib/kameleoon/configuration/rule.rb +57 -0
- data/lib/kameleoon/configuration/settings.rb +20 -0
- data/lib/kameleoon/configuration/variable.rb +23 -0
- data/lib/kameleoon/configuration/variation.rb +31 -0
- data/lib/kameleoon/configuration/variation_exposition.rb +23 -0
- data/lib/kameleoon/cookie.rb +13 -6
- data/lib/kameleoon/data.rb +60 -40
- data/lib/kameleoon/exceptions.rb +46 -23
- data/lib/kameleoon/factory.rb +21 -18
- data/lib/kameleoon/hybrid/manager.rb +60 -0
- data/lib/kameleoon/real_time/real_time_configuration_service.rb +98 -0
- data/lib/kameleoon/real_time/real_time_event.rb +22 -0
- data/lib/kameleoon/real_time/sse_client.rb +111 -0
- data/lib/kameleoon/real_time/sse_message.rb +23 -0
- data/lib/kameleoon/real_time/sse_request.rb +59 -0
- data/lib/kameleoon/request.rb +14 -13
- data/lib/kameleoon/storage/cache.rb +84 -0
- data/lib/kameleoon/storage/cache_factory.rb +23 -0
- data/lib/kameleoon/storage/variation_storage.rb +42 -0
- data/lib/kameleoon/storage/visitor_variation.rb +20 -0
- data/lib/kameleoon/targeting/condition.rb +17 -5
- data/lib/kameleoon/targeting/condition_factory.rb +9 -2
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +67 -48
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +29 -0
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +44 -0
- data/lib/kameleoon/targeting/models.rb +36 -36
- data/lib/kameleoon/utils.rb +4 -1
- data/lib/kameleoon/version.rb +4 -2
- metadata +35 -3
- data/lib/kameleoon/query_graphql.rb +0 -76
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Kameleoon Real Time Configuration Service
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
require 'kameleoon/real_time/real_time_event'
|
7
|
+
require 'kameleoon/real_time/sse_request'
|
8
|
+
require 'kameleoon/real_time/sse_client'
|
9
|
+
|
10
|
+
module Kameleoon
|
11
|
+
module RealTime
|
12
|
+
CONFIGURATION_UPDATE_EVENT = 'configuration-update-event'
|
13
|
+
|
14
|
+
##
|
15
|
+
# RealTimeConfigurationService is used for fetching updates of configuration
|
16
|
+
# (experiments and feature flags) in real time.
|
17
|
+
class RealTimeConfigurationService
|
18
|
+
##
|
19
|
+
# Parametrized initializer.
|
20
|
+
#
|
21
|
+
# @param url [String]
|
22
|
+
# @param update_handler [Callable[Kameleoon::RealTime::RealTimeEvent] | NilClass] Handler which
|
23
|
+
# is synchronously called for gotten RealTimeEvent objects.
|
24
|
+
# @param log_func [Callable[String] | NilClass] Callable object which synchronously called to log.
|
25
|
+
def initialize(url, update_handler, log_func, sse_request_source = nil)
|
26
|
+
@url = url
|
27
|
+
@update_handler = update_handler
|
28
|
+
@need_close = false
|
29
|
+
@headers = {
|
30
|
+
'Accept': 'text/event-stream',
|
31
|
+
'Cache-Control': 'no-cache',
|
32
|
+
'Connection': 'Keep-Alive'
|
33
|
+
}
|
34
|
+
@log_func = log_func
|
35
|
+
@sse_request_source = sse_request_source
|
36
|
+
@sse_thread = nil
|
37
|
+
@sse_client = nil
|
38
|
+
create_sse_client
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Closes the connection to the server.
|
43
|
+
def close
|
44
|
+
return if @need_close
|
45
|
+
|
46
|
+
@log_func&.call('Real-time configuration service is shutting down')
|
47
|
+
@need_close = true
|
48
|
+
return if @sse_thread.nil?
|
49
|
+
|
50
|
+
@sse_thread.kill
|
51
|
+
@sse_thread = nil
|
52
|
+
@sse_client.call_close_handler
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def create_sse_client
|
58
|
+
@sse_thread = Thread.new { run_sse_client } unless @need_close
|
59
|
+
end
|
60
|
+
|
61
|
+
def init_sse_client
|
62
|
+
message_handler = proc do |message|
|
63
|
+
@log_func&.call("Got SSE event: #{message.event}")
|
64
|
+
if message.event == CONFIGURATION_UPDATE_EVENT
|
65
|
+
event_dict = JSON.parse(message.data)
|
66
|
+
@update_handler&.call(RealTimeEvent.new(event_dict))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
sse_request = make_sse_request
|
70
|
+
@sse_client = SseClient.new(sse_request, message_handler)
|
71
|
+
@log_func&.call('Created SSE client')
|
72
|
+
end
|
73
|
+
|
74
|
+
def make_sse_request
|
75
|
+
open_handler = proc { @log_func&.call('SSE connection open') }
|
76
|
+
close_handler = proc { @log_func&.call('SSE connection closed') }
|
77
|
+
unexpected_status_code_handler =
|
78
|
+
proc { |resp| @log_func&.call("Unexpected status code of SSE response: #{resp.code}") }
|
79
|
+
if @sse_request_source.nil?
|
80
|
+
return SseRequest.new(@url, @headers, open_handler, close_handler, unexpected_status_code_handler)
|
81
|
+
end
|
82
|
+
|
83
|
+
@sse_request_source.call(@url, @headers, open_handler, close_handler, unexpected_status_code_handler)
|
84
|
+
end
|
85
|
+
|
86
|
+
def run_sse_client
|
87
|
+
until @need_close
|
88
|
+
init_sse_client
|
89
|
+
begin
|
90
|
+
@sse_client.start
|
91
|
+
rescue StandardError => e
|
92
|
+
@log_func&.call("Error occurred within SSE client: #{e}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Kameleoon Real Time Event
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module RealTime
|
7
|
+
##
|
8
|
+
# RealTimeEvent contains information about timestamp when configuration was updated.
|
9
|
+
# Timestamp parameter is used to fetch the latest configuration.
|
10
|
+
class RealTimeEvent
|
11
|
+
attr_reader :time_stamp
|
12
|
+
|
13
|
+
##
|
14
|
+
# Parametrized initializer. Gets the new time stamp from a JSON message.
|
15
|
+
#
|
16
|
+
# @param event_dict [Hash] Hash of SSE message data.
|
17
|
+
def initialize(event_dict)
|
18
|
+
@time_stamp = event_dict['ts']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/real_time/sse_message'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module RealTime
|
7
|
+
##
|
8
|
+
# SseClient is used to interpret SSE event stream.
|
9
|
+
class SseClient
|
10
|
+
##
|
11
|
+
# Parametrized initializer.
|
12
|
+
#
|
13
|
+
# @param sse_request [Kameleoon::RealTime::SseRequest] Used to access SSE event stream.
|
14
|
+
# @param message_handler [Callable[Kameleoon::RealTime::SseMessage] | NilClass] Callable object which
|
15
|
+
# is synchronously called for received SSE messages.
|
16
|
+
def initialize(sse_request, message_handler)
|
17
|
+
@sse_request = sse_request
|
18
|
+
@message_handler = message_handler
|
19
|
+
|
20
|
+
@cr_prev = nil
|
21
|
+
@buffer = nil
|
22
|
+
@data_buffer = nil
|
23
|
+
@event = nil
|
24
|
+
@id = nil
|
25
|
+
@reconnection_time = nil
|
26
|
+
|
27
|
+
@sse_request.resp_char_handler = method(:process_char)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Starts SSE connection and stay in the loop until close.
|
32
|
+
def start
|
33
|
+
@cr_prev = false
|
34
|
+
@data_buffer = []
|
35
|
+
@event = nil
|
36
|
+
@id = nil
|
37
|
+
@buffer = []
|
38
|
+
@sse_request.start
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Calls @sse_request@close_handler if it is not nil.
|
43
|
+
def call_close_handler
|
44
|
+
@sse_request.call_close_handler
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def process_char(ch)
|
50
|
+
if @cr_prev && (ch == "\n")
|
51
|
+
@cr_prev = false
|
52
|
+
return
|
53
|
+
end
|
54
|
+
@cr_prev = ch == "\r"
|
55
|
+
if @cr_prev || (ch == "\n")
|
56
|
+
line = @buffer.join
|
57
|
+
@buffer.clear
|
58
|
+
if line.empty?
|
59
|
+
dispatch_event
|
60
|
+
else
|
61
|
+
handle_line(line)
|
62
|
+
end
|
63
|
+
return
|
64
|
+
end
|
65
|
+
@buffer << ch
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_line(line)
|
69
|
+
field, value = parse_line(line)
|
70
|
+
return if field.nil?
|
71
|
+
|
72
|
+
case field
|
73
|
+
when 'event'
|
74
|
+
@event = value
|
75
|
+
when 'data'
|
76
|
+
@data_buffer << value
|
77
|
+
when 'id'
|
78
|
+
@id = value
|
79
|
+
when 'retry'
|
80
|
+
@reconnection_time = value.to_i # This does not affect anything
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_line(line)
|
85
|
+
colon_index = line.index(':')
|
86
|
+
return line, nil if colon_index.nil?
|
87
|
+
return nil, nil if colon_index.zero?
|
88
|
+
|
89
|
+
field = line[0, colon_index]
|
90
|
+
return field, '' if colon_index + 1 == line.length
|
91
|
+
|
92
|
+
value = if line[colon_index + 1] == ' '
|
93
|
+
line[colon_index + 2..]
|
94
|
+
else
|
95
|
+
line[colon_index + 1..]
|
96
|
+
end
|
97
|
+
[field, value]
|
98
|
+
end
|
99
|
+
|
100
|
+
def dispatch_event
|
101
|
+
return unless !@event.nil? || !@data_buffer.empty? # Ignoring empty events
|
102
|
+
|
103
|
+
data = @data_buffer.join("\n")
|
104
|
+
message = SseMessage.new(@event, @id, data)
|
105
|
+
@event = nil
|
106
|
+
@data_buffer.clear
|
107
|
+
@message_handler&.call(message)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module RealTime
|
5
|
+
##
|
6
|
+
# SseMessage is a container for a received SSE message.
|
7
|
+
class SseMessage
|
8
|
+
attr_reader :event, :id, :data
|
9
|
+
|
10
|
+
##
|
11
|
+
# Parametrized initializer.
|
12
|
+
#
|
13
|
+
# @param event [String]
|
14
|
+
# @param id [String]
|
15
|
+
# @param data [String]
|
16
|
+
def initialize(event, id, data)
|
17
|
+
@event = event
|
18
|
+
@id = id
|
19
|
+
@data = data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'restclient'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module RealTime
|
7
|
+
##
|
8
|
+
# SseRequest is used keep SSE connection and read from its stream.
|
9
|
+
class SseRequest
|
10
|
+
attr_writer :resp_char_handler
|
11
|
+
|
12
|
+
##
|
13
|
+
# Parametrized initializer.
|
14
|
+
#
|
15
|
+
# @param url [String]
|
16
|
+
# @param headers [Hash]
|
17
|
+
# @param open_handler [Callable | NilClass] Handler which is synchronously called when SSE connection is open.
|
18
|
+
# @param close_handler [Callable | NilClass] Handler which is synchronously called when SSE connection is closed.
|
19
|
+
# @param unexpected_status_code_handler [Callable[Net::HTTPResponse] | NilClass] Handler whick
|
20
|
+
# is synchronously called for responses with not 200 status code.
|
21
|
+
def initialize(url, headers, open_handler, close_handler, unexpected_status_code_handler)
|
22
|
+
@url = url
|
23
|
+
@headers = headers
|
24
|
+
@open_handler = open_handler
|
25
|
+
@close_handler = close_handler
|
26
|
+
@unexpected_status_code_handler = unexpected_status_code_handler
|
27
|
+
|
28
|
+
@resp_char_handler = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Starts SSE connection and stay in the loop until close.
|
33
|
+
def start
|
34
|
+
RestClient::Request.execute(method: :get, url: @url, headers: @headers, read_timeout: nil,
|
35
|
+
block_response: method(:handle_resp))
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Calls @close_handler if it is not nil.
|
40
|
+
def call_close_handler
|
41
|
+
@close_handler&.call
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def handle_resp(resp)
|
47
|
+
@open_handler&.call
|
48
|
+
if resp.code == '200'
|
49
|
+
resp.read_body do |chunk|
|
50
|
+
chunk.each_char { |ch| @resp_char_handler&.call(ch) }
|
51
|
+
end
|
52
|
+
else
|
53
|
+
@unexpected_status_code_handler&.call(resp)
|
54
|
+
end
|
55
|
+
call_close_handler
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/kameleoon/request.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'em-synchrony/em-http'
|
2
|
+
require 'kameleoon/version'
|
3
3
|
require 'net/http'
|
4
4
|
|
5
5
|
module Kameleoon
|
6
6
|
# @api private
|
7
7
|
module Request
|
8
8
|
protected
|
9
|
-
|
9
|
+
|
10
|
+
API_URL = 'https://api.kameleoon.com'.freeze
|
11
|
+
CLIENT_CONFIG_URL = 'https://client-config.kameleoon.com'.freeze
|
10
12
|
|
11
13
|
module Method
|
12
|
-
GET =
|
13
|
-
POST =
|
14
|
+
GET = 'get'.freeze
|
15
|
+
POST = 'post'.freeze
|
14
16
|
end
|
15
17
|
|
16
18
|
def get(request_options, url = API_URL, connexion_options = {})
|
@@ -32,7 +34,7 @@ module Kameleoon
|
|
32
34
|
private
|
33
35
|
|
34
36
|
def request(method, request_options, url, connexion_options)
|
35
|
-
connexion_options[:tls] = {verify_peer: false}
|
37
|
+
connexion_options[:tls] = { verify_peer: false }
|
36
38
|
add_user_agent(request_options)
|
37
39
|
case method
|
38
40
|
when Method::POST then
|
@@ -40,7 +42,7 @@ module Kameleoon
|
|
40
42
|
when Method::GET then
|
41
43
|
return EventMachine::HttpRequest.new(url, connexion_options).get request_options
|
42
44
|
else
|
43
|
-
print
|
45
|
+
print 'Unknown request type'
|
44
46
|
return false
|
45
47
|
end
|
46
48
|
end
|
@@ -68,22 +70,21 @@ module Kameleoon
|
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
|
-
def
|
73
|
+
def successful?(request)
|
72
74
|
!request.nil? && request != false && /20\d/.match(request.response_header.status.to_s)
|
73
75
|
end
|
74
76
|
|
75
|
-
def
|
77
|
+
def successful_sync?(response)
|
76
78
|
!response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
|
77
79
|
end
|
78
80
|
|
79
81
|
def add_user_agent(request_options)
|
82
|
+
sdk_version = "sdk/ruby/#{Kameleoon::VERSION}"
|
80
83
|
if request_options[:head].nil?
|
81
|
-
request_options[:head] = {'Kameleoon-Client' =>
|
84
|
+
request_options[:head] = { 'Kameleoon-Client' => sdk_version }
|
82
85
|
else
|
83
|
-
request_options[:head].store('Kameleoon-Client',
|
86
|
+
request_options[:head].store('Kameleoon-Client', sdk_version)
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
87
90
|
end
|
88
|
-
|
89
|
-
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module Storage
|
7
|
+
# Will be useful for Ruby 3.0
|
8
|
+
# Abstract Cache class (interface)
|
9
|
+
class Cache
|
10
|
+
def get(_key)
|
11
|
+
raise 'Abstract method `read` called'
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(_key, _value)
|
15
|
+
raise 'Abstract method `write` called'
|
16
|
+
end
|
17
|
+
|
18
|
+
def active_items
|
19
|
+
raise 'Abstract method `active_items` called'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Implementation of Cache with auto cleaning feature
|
24
|
+
class CacheImpl < Cache
|
25
|
+
def initialize(expiration_time, cleaning_interval)
|
26
|
+
super()
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@expiration_time = expiration_time
|
29
|
+
@cleaning_interval = cleaning_interval
|
30
|
+
@cache = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(key, value)
|
34
|
+
@mutex.synchronize do
|
35
|
+
start_cleaner_timer if @cleaning_interval.positive? && @cleaner_timer.nil?
|
36
|
+
@cache[key] = { value: value, expired: Time.now + @expiration_time }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(key)
|
41
|
+
entry = @cache[key]
|
42
|
+
return entry[:value] unless entry.nil? || expired?(entry)
|
43
|
+
|
44
|
+
@mutex.synchronize { @cache.delete(key) }
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def active_items
|
49
|
+
active_items = {}
|
50
|
+
remove_expired_entries
|
51
|
+
@mutex.synchronize do
|
52
|
+
@cache.each_pair { |key, entry| active_items[key] = entry[:value] }
|
53
|
+
end
|
54
|
+
active_items
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def start_cleaner_timer
|
60
|
+
@cleaner_timer = Concurrent::TimerTask.new(execution_interval: @cleaning_interval) do
|
61
|
+
remove_expired_entries
|
62
|
+
end
|
63
|
+
@cleaner_timer.execute
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop_cleaner_timer
|
67
|
+
@cleaner_timer&.shutdown
|
68
|
+
@cleaner_timer = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove_expired_entries
|
72
|
+
time = Time.now
|
73
|
+
@mutex.synchronize do
|
74
|
+
@cache.delete_if { |_, entry| expired?(entry, time) }
|
75
|
+
stop_cleaner_timer if @cache.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def expired?(entry, time = Time.now)
|
80
|
+
entry[:expired] <= time
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require_relative 'cache'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
module Storage
|
8
|
+
# Will be useful for Ruby 3.0
|
9
|
+
# Abstract CacheFactory class (interface)
|
10
|
+
class CacheFactory
|
11
|
+
def create(_experiration_time, _cleaning_time)
|
12
|
+
raise 'Abstract method `create` called'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Implementation of CacheFactory with auto cleaning feature
|
17
|
+
class CacheFactoryImpl < CacheFactory
|
18
|
+
def create(expiration_time, cleaning_interval)
|
19
|
+
CacheImpl.new(expiration_time, cleaning_interval)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/storage/visitor_variation'
|
4
|
+
|
5
|
+
module Kameleoon
|
6
|
+
module Storage
|
7
|
+
# VariationStorage is a container for saved variations associated with a visitor
|
8
|
+
class VariationStorage
|
9
|
+
def initialize
|
10
|
+
@storage = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_variation_id(visitor_code, experiment_id)
|
14
|
+
variation_valid?(visitor_code, experiment_id, nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def variation_valid?(visitor_code, experiment_id, respool_time)
|
18
|
+
return nil if @storage[visitor_code].nil? || @storage[visitor_code][experiment_id].nil?
|
19
|
+
|
20
|
+
variation = @storage[visitor_code][experiment_id]
|
21
|
+
return nil unless variation.valid?(respool_time)
|
22
|
+
|
23
|
+
variation.variation_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_variation(visitor_code, experiment_id, variation_id)
|
27
|
+
@storage[visitor_code] = {} if @storage[visitor_code].nil?
|
28
|
+
@storage[visitor_code][experiment_id] = Kameleoon::Storage::VisitorVariation.new(variation_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_hash_saved_variation_id(visitor_code)
|
32
|
+
return nil if @storage[visitor_code].nil?
|
33
|
+
|
34
|
+
map_variations = {}
|
35
|
+
@storage[visitor_code].each do |key, value|
|
36
|
+
map_variations[key] = value.variation_id
|
37
|
+
end
|
38
|
+
map_variations
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kameleoon
|
4
|
+
module Storage
|
5
|
+
# VisitorVariation contains a saved variation id associated with a visitor
|
6
|
+
# and time when it was associated.
|
7
|
+
class VisitorVariation
|
8
|
+
attr_accessor :variation_id
|
9
|
+
|
10
|
+
def initialize(variation_id)
|
11
|
+
@variation_id = variation_id
|
12
|
+
@assignment_date = Time.now.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?(respool_time)
|
16
|
+
respool_time.nil? || @assignment_date > respool_time
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,16 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Kameleoon
|
2
|
-
|
4
|
+
# @api private
|
3
5
|
module Targeting
|
4
6
|
class Condition
|
5
7
|
attr_accessor :type, :include
|
6
8
|
|
7
|
-
def initialize(
|
8
|
-
|
9
|
+
def initialize(json_condition)
|
10
|
+
if json_condition['targetingType'].nil?
|
11
|
+
raise Exception::NotFound.new('targetingType'), 'targetingType missed'
|
12
|
+
end
|
13
|
+
|
14
|
+
@type = json_condition['targetingType']
|
15
|
+
|
16
|
+
if json_condition['include'].nil? && json_condition['isInclude'].nil?
|
17
|
+
raise Exception::NotFound.new('include / isInclude missed'), 'include / isInclude missed'
|
18
|
+
end
|
19
|
+
|
20
|
+
@include = json_condition['include'] || json_condition['isInclude']
|
9
21
|
end
|
10
22
|
|
11
23
|
def check(conditions)
|
12
|
-
raise
|
24
|
+
raise 'Abstract method `check` call'
|
13
25
|
end
|
14
26
|
end
|
15
27
|
end
|
16
|
-
end
|
28
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'kameleoon/targeting/conditions/custom_datum'
|
2
|
+
require 'kameleoon/targeting/conditions/target_experiment'
|
3
|
+
require 'kameleoon/targeting/conditions/exclusive_experiment'
|
2
4
|
|
3
5
|
module Kameleoon
|
4
6
|
#@api private
|
@@ -6,11 +8,16 @@ module Kameleoon
|
|
6
8
|
module ConditionFactory
|
7
9
|
def get_condition(condition_json)
|
8
10
|
condition = nil
|
9
|
-
|
11
|
+
case condition_json['targetingType']
|
12
|
+
when ConditionType::CUSTOM_DATUM.to_s
|
10
13
|
condition = CustomDatum.new(condition_json)
|
14
|
+
when ConditionType::TARGET_EXPERIMENT.to_s
|
15
|
+
condition = TargetExperiment.new(condition_json)
|
16
|
+
when ConditionType::EXCLUSIVE_EXPERIMENT.to_s
|
17
|
+
condition = ExclusiveExperiment.new(condition_json)
|
11
18
|
end
|
12
19
|
condition
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
16
|
-
end
|
23
|
+
end
|