kameleoon-client-ruby 2.0.0 → 2.1.1
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 +277 -260
- data/lib/kameleoon/configuration/feature_flag.rb +13 -24
- data/lib/kameleoon/configuration/rule.rb +17 -5
- data/lib/kameleoon/configuration/settings.rb +20 -0
- data/lib/kameleoon/cookie.rb +3 -3
- data/lib/kameleoon/data/browser.rb +33 -0
- data/lib/kameleoon/data/conversion.rb +26 -0
- data/lib/kameleoon/data/custom_data.rb +53 -0
- data/lib/kameleoon/data/data.rb +35 -0
- data/lib/kameleoon/data/device.rb +26 -0
- data/lib/kameleoon/data/page_view.rb +31 -0
- data/lib/kameleoon/data/user_agent.rb +14 -0
- data/lib/kameleoon/hybrid/manager.rb +60 -0
- data/lib/kameleoon/network/activity_event.rb +31 -0
- data/lib/kameleoon/network/experiment_event.rb +35 -0
- data/lib/kameleoon/network/uri_helper.rb +36 -0
- data/lib/kameleoon/network/url_provider.rb +71 -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 +5 -19
- data/lib/kameleoon/storage/cache.rb +84 -0
- data/lib/kameleoon/storage/cache_factory.rb +23 -0
- data/lib/kameleoon/targeting/condition.rb +41 -12
- data/lib/kameleoon/targeting/condition_factory.rb +35 -12
- data/lib/kameleoon/targeting/conditions/browser_condition.rb +71 -0
- data/lib/kameleoon/targeting/conditions/conversion_condition.rb +21 -0
- data/lib/kameleoon/targeting/conditions/custom_datum.rb +64 -34
- data/lib/kameleoon/targeting/conditions/device_condition.rb +21 -0
- data/lib/kameleoon/targeting/conditions/exclusive_experiment.rb +0 -12
- data/lib/kameleoon/targeting/conditions/page_title_condition.rb +21 -0
- data/lib/kameleoon/targeting/conditions/page_url_condition.rb +21 -0
- data/lib/kameleoon/targeting/conditions/sdk_language_condition.rb +65 -0
- data/lib/kameleoon/targeting/conditions/string_value_condition.rb +40 -0
- data/lib/kameleoon/targeting/conditions/target_experiment.rb +5 -9
- data/lib/kameleoon/targeting/conditions/unknown_condition.rb +15 -0
- data/lib/kameleoon/targeting/conditions/visitor_code_condition.rb +16 -0
- data/lib/kameleoon/targeting/models.rb +0 -24
- data/lib/kameleoon/utils.rb +1 -1
- data/lib/kameleoon/version.rb +28 -1
- metadata +45 -4
- data/lib/kameleoon/configuration/feature_flag_v2.rb +0 -30
- data/lib/kameleoon/data.rb +0 -169
@@ -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
@@ -7,27 +7,24 @@ module Kameleoon
|
|
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
|
12
|
-
|
13
10
|
module Method
|
14
11
|
GET = 'get'.freeze
|
15
12
|
POST = 'post'.freeze
|
16
13
|
end
|
17
14
|
|
18
|
-
def get(request_options, url
|
15
|
+
def get(request_options, url, connexion_options = {})
|
19
16
|
request(Method::GET, request_options, url, connexion_options)
|
20
17
|
end
|
21
18
|
|
22
|
-
def post(request_options, url
|
19
|
+
def post(request_options, url, connexion_options = {})
|
23
20
|
request(Method::POST, request_options, url, connexion_options)
|
24
21
|
end
|
25
22
|
|
26
|
-
def get_sync(url
|
23
|
+
def get_sync(url, connexion_options = {})
|
27
24
|
request_sync(Method::GET, url, connexion_options, {})
|
28
25
|
end
|
29
26
|
|
30
|
-
def post_sync(request_options, url
|
27
|
+
def post_sync(request_options, url, connexion_options = {})
|
31
28
|
request_sync(Method::POST, url, connexion_options, request_options)
|
32
29
|
end
|
33
30
|
|
@@ -35,7 +32,6 @@ module Kameleoon
|
|
35
32
|
|
36
33
|
def request(method, request_options, url, connexion_options)
|
37
34
|
connexion_options[:tls] = { verify_peer: false }
|
38
|
-
add_user_agent(request_options)
|
39
35
|
case method
|
40
36
|
when Method::POST then
|
41
37
|
return EventMachine::HttpRequest.new(url, connexion_options).apost request_options
|
@@ -49,7 +45,6 @@ module Kameleoon
|
|
49
45
|
|
50
46
|
def request_sync(method, url, connexion_options, request_options)
|
51
47
|
request_options = {} if request_options.nil?
|
52
|
-
add_user_agent(request_options)
|
53
48
|
case method
|
54
49
|
when Method::GET then
|
55
50
|
uri = URI(url)
|
@@ -60,7 +55,7 @@ module Kameleoon
|
|
60
55
|
uri = URI.parse(url)
|
61
56
|
http = Net::HTTP.new(uri.host, uri.port)
|
62
57
|
http.use_ssl = true
|
63
|
-
request = Net::HTTP::Post.new(
|
58
|
+
request = Net::HTTP::Post.new(uri, initheader = request_options[:head])
|
64
59
|
request.body = request_options[:body]
|
65
60
|
response = http.request(request)
|
66
61
|
response
|
@@ -77,14 +72,5 @@ module Kameleoon
|
|
77
72
|
def successful_sync?(response)
|
78
73
|
!response.nil? && response != false && response.is_a?(Net::HTTPSuccess)
|
79
74
|
end
|
80
|
-
|
81
|
-
def add_user_agent(request_options)
|
82
|
-
sdk_version = "sdk/ruby/#{Kameleoon::VERSION}"
|
83
|
-
if request_options[:head].nil?
|
84
|
-
request_options[:head] = { 'Kameleoon-Client' => sdk_version }
|
85
|
-
else
|
86
|
-
request_options[:head].store('Kameleoon-Client', sdk_version)
|
87
|
-
end
|
88
|
-
end
|
89
75
|
end
|
90
76
|
end
|
@@ -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
|
@@ -1,28 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'kameleoon/exceptions'
|
2
3
|
|
3
4
|
module Kameleoon
|
4
5
|
# @api private
|
5
6
|
module Targeting
|
7
|
+
module ConditionType
|
8
|
+
CUSTOM_DATUM = 'CUSTOM_DATUM'
|
9
|
+
TARGET_EXPERIMENT = 'TARGET_EXPERIMENT'
|
10
|
+
EXCLUSIVE_EXPERIMENT = 'EXCLUSIVE_EXPERIMENT'
|
11
|
+
PAGE_URL = 'PAGE_URL'
|
12
|
+
PAGE_TITLE = 'PAGE_TITLE'
|
13
|
+
VISITOR_CODE = 'VISITOR_CODE'
|
14
|
+
DEVICE_TYPE = 'DEVICE_TYPE'
|
15
|
+
BROWSER = 'BROWSER'
|
16
|
+
SDK_LANGUAGE = 'SDK_LANGUAGE'
|
17
|
+
CONVERSIONS = 'CONVERSIONS'
|
18
|
+
end
|
19
|
+
|
20
|
+
module Operator
|
21
|
+
UNDEFINED = 'UNDEFINED'
|
22
|
+
CONTAINS = 'CONTAINS'
|
23
|
+
EXACT = 'EXACT'
|
24
|
+
REGULAR_EXPRESSION = 'REGULAR_EXPRESSION'
|
25
|
+
LOWER = 'LOWER'
|
26
|
+
EQUAL = 'EQUAL'
|
27
|
+
GREATER = 'GREATER'
|
28
|
+
IS_TRUE = 'TRUE'
|
29
|
+
IS_FALSE = 'FALSE'
|
30
|
+
AMONG_VALUES = 'AMONG_VALUES'
|
31
|
+
ANY = 'ANY'
|
32
|
+
UNKNOWN = 'UNKNOWN'
|
33
|
+
end
|
34
|
+
|
35
|
+
# Base class for all targeting conditions
|
6
36
|
class Condition
|
7
|
-
|
37
|
+
attr_reader :type, :include
|
8
38
|
|
9
39
|
def initialize(json_condition)
|
10
|
-
if json_condition['targetingType'].nil?
|
11
|
-
raise Exception::NotFoundError.new('targetingType'), 'targetingType missed'
|
12
|
-
end
|
40
|
+
raise Exception::NotFound.new('targetingType'), 'targetingType missed' if json_condition['targetingType'].nil?
|
13
41
|
|
14
42
|
@type = json_condition['targetingType']
|
43
|
+
@include = json_condition['isInclude'].nil? ? true : json_condition['isInclude']
|
44
|
+
end
|
15
45
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
@include = json_condition['include'] || json_condition['isInclude']
|
46
|
+
def check(_data)
|
47
|
+
raise 'Abstract method `check` call'
|
21
48
|
end
|
22
49
|
|
23
|
-
|
24
|
-
|
50
|
+
protected
|
51
|
+
|
52
|
+
def get_last_targeting_data(list_data, data_type)
|
53
|
+
list_data.select { |data| data.instance == data_type }.last
|
25
54
|
end
|
26
55
|
end
|
27
56
|
end
|
28
|
-
end
|
57
|
+
end
|
@@ -1,22 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'conditions/custom_datum'
|
2
|
+
require_relative 'conditions/target_experiment'
|
3
|
+
require_relative 'conditions/exclusive_experiment'
|
4
|
+
require_relative 'conditions/page_title_condition'
|
5
|
+
require_relative 'conditions/page_url_condition'
|
6
|
+
require_relative 'conditions/visitor_code_condition'
|
7
|
+
require_relative 'conditions/device_condition'
|
8
|
+
require_relative 'conditions/conversion_condition'
|
9
|
+
require_relative 'conditions/browser_condition'
|
10
|
+
require_relative 'conditions/sdk_language_condition'
|
11
|
+
require_relative 'conditions/unknown_condition'
|
4
12
|
|
5
13
|
module Kameleoon
|
6
|
-
|
14
|
+
# @api private
|
7
15
|
module Targeting
|
16
|
+
# Module for create different targeting conditions
|
8
17
|
module ConditionFactory
|
9
18
|
def get_condition(condition_json)
|
10
|
-
condition = nil
|
11
19
|
case condition_json['targetingType']
|
12
|
-
when ConditionType::CUSTOM_DATUM
|
13
|
-
|
14
|
-
when ConditionType::TARGET_EXPERIMENT
|
15
|
-
|
16
|
-
when ConditionType::EXCLUSIVE_EXPERIMENT
|
17
|
-
|
20
|
+
when ConditionType::CUSTOM_DATUM
|
21
|
+
CustomDatum.new(condition_json)
|
22
|
+
when ConditionType::TARGET_EXPERIMENT
|
23
|
+
TargetExperiment.new(condition_json)
|
24
|
+
when ConditionType::EXCLUSIVE_EXPERIMENT
|
25
|
+
ExclusiveExperiment.new(condition_json)
|
26
|
+
when ConditionType::PAGE_URL
|
27
|
+
PageUrlCondition.new(condition_json)
|
28
|
+
when ConditionType::PAGE_TITLE
|
29
|
+
PageTitleCondition.new(condition_json)
|
30
|
+
when ConditionType::VISITOR_CODE
|
31
|
+
VisitorCodeCondition.new(condition_json)
|
32
|
+
when ConditionType::DEVICE_TYPE
|
33
|
+
DeviceCondition.new(condition_json)
|
34
|
+
when ConditionType::CONVERSIONS
|
35
|
+
ConversionCondition.new(condition_json)
|
36
|
+
when ConditionType::BROWSER
|
37
|
+
BrowserCondition.new(condition_json)
|
38
|
+
when ConditionType::SDK_LANGUAGE
|
39
|
+
SdkLanguageCondition.new(condition_json)
|
40
|
+
else
|
41
|
+
UnknownCondition.new(condition_json)
|
18
42
|
end
|
19
|
-
condition
|
20
43
|
end
|
21
44
|
end
|
22
45
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kameleoon/data/browser'
|
4
|
+
require 'kameleoon/version'
|
5
|
+
|
6
|
+
module Kameleoon
|
7
|
+
# @api private
|
8
|
+
module Targeting
|
9
|
+
|
10
|
+
CHROME = 'CHROME'
|
11
|
+
INTERNET_EXPLORER = 'IE'
|
12
|
+
FIREFOX = 'FIREFOX'
|
13
|
+
SAFARI = 'SAFARI'
|
14
|
+
OPERA = 'OPERA'
|
15
|
+
|
16
|
+
# BrowserCondition is a condition for checking targeting with browser's type and version
|
17
|
+
class BrowserCondition < Condition
|
18
|
+
def initialize(json_condition)
|
19
|
+
super(json_condition)
|
20
|
+
@browser_type = browser_index_from_name(json_condition['browser'])
|
21
|
+
@version = json_condition['version']
|
22
|
+
@version_match_type = json_condition['versionMatchType']
|
23
|
+
end
|
24
|
+
|
25
|
+
def check(list_data)
|
26
|
+
browser = get_last_targeting_data(list_data, Kameleoon::DataType::BROWSER)
|
27
|
+
browser && check_targeting(browser)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def check_targeting(browser)
|
33
|
+
return false if @browser_type != browser.type
|
34
|
+
|
35
|
+
return true if @version.nil?
|
36
|
+
|
37
|
+
version_number = SdkVersion.get_float_version(@version)
|
38
|
+
return false if version_number.nan?
|
39
|
+
|
40
|
+
case @version_match_type
|
41
|
+
when Operator::EQUAL
|
42
|
+
browser.version == version_number
|
43
|
+
when Operator::GREATER
|
44
|
+
browser.version > version_number
|
45
|
+
when Operator::LOWER
|
46
|
+
browser.version < version_number
|
47
|
+
else
|
48
|
+
puts "Unexpected comparing operation for Browser condition: #{@version_match_type}"
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def browser_index_from_name(name)
|
54
|
+
case name
|
55
|
+
when CHROME
|
56
|
+
BrowserType::CHROME
|
57
|
+
when INTERNET_EXPLORER
|
58
|
+
BrowserType::INTERNET_EXPLORER
|
59
|
+
when FIREFOX
|
60
|
+
BrowserType::FIREFOX
|
61
|
+
when SAFARI
|
62
|
+
BrowserType::SAFARI
|
63
|
+
when OPERA
|
64
|
+
BrowserType::OPERA
|
65
|
+
else
|
66
|
+
BrowserType::OTHER
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|