rox-rollout 5.0.3 → 5.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/.github/workflows/e2e_tests.yaml +52 -0
- data/.github/workflows/{ruby.yml → unit_tests.yaml} +4 -5
- data/.gitignore +1 -0
- data/Rakefile +0 -6
- data/e2e-server/.gitignore +1 -0
- data/e2e-server/run_server.sh +7 -3
- data/e2e-server/server.rb +26 -56
- data/example/local.rb +7 -7
- data/lib/rox/core/analytics/backoff_policy.rb +51 -0
- data/lib/rox/core/analytics/client.rb +187 -0
- data/lib/rox/core/analytics/defaults.rb +31 -0
- data/lib/rox/core/analytics/logging.rb +62 -0
- data/lib/rox/core/analytics/message_batch.rb +74 -0
- data/lib/rox/core/analytics/response.rb +17 -0
- data/lib/rox/core/analytics/test_queue.rb +58 -0
- data/lib/rox/core/analytics/transport.rb +144 -0
- data/lib/rox/core/analytics/utils.rb +89 -0
- data/lib/rox/core/analytics/worker.rb +67 -0
- data/lib/rox/core/consts/environment.rb +62 -54
- data/lib/rox/core/core.rb +19 -9
- data/lib/rox/core/entities/flag.rb +3 -1
- data/lib/rox/core/entities/rox_double.rb +3 -1
- data/lib/rox/core/entities/rox_int.rb +3 -1
- data/lib/rox/core/entities/rox_string.rb +3 -2
- data/lib/rox/core/impression/impression_invoker.rb +4 -6
- data/lib/rox/core/network/request_configuration_builder.rb +1 -1
- data/lib/rox/core/network/response.rb +2 -2
- data/lib/rox/core/network/state_sender.rb +1 -1
- data/lib/rox/server/network_configurations_options.rb +20 -0
- data/lib/rox/server/rox_options.rb +3 -9
- data/lib/rox/server/self_managed_options.rb +12 -0
- data/lib/rox/version.rb +1 -1
- data/rox.gemspec +2 -2
- metadata +25 -16
- data/.circleci/config.yml +0 -73
- data/e2e/container.rb +0 -35
- data/e2e/custom_props.rb +0 -55
- data/e2e/rox_e2e_test.rb +0 -157
- data/e2e/test_vars.rb +0 -21
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'rox/core/analytics/logging'
|
3
|
+
|
4
|
+
module Rox
|
5
|
+
module Core
|
6
|
+
class Analytics
|
7
|
+
# A batch of `Message`s to be sent to the API
|
8
|
+
class MessageBatch
|
9
|
+
class JSONGenerationError < StandardError; end
|
10
|
+
|
11
|
+
extend Forwardable
|
12
|
+
include Rox::Core::Analytics::Logging
|
13
|
+
include Rox::Core::Analytics::Defaults::MessageBatch
|
14
|
+
|
15
|
+
def initialize(max_message_count)
|
16
|
+
@messages = []
|
17
|
+
@max_message_count = max_message_count
|
18
|
+
@json_size = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(message)
|
22
|
+
begin
|
23
|
+
message_json = message.to_json
|
24
|
+
rescue StandardError => e
|
25
|
+
raise JSONGenerationError, "Serialization error: #{e}"
|
26
|
+
end
|
27
|
+
|
28
|
+
message_json_size = message_json.bytesize
|
29
|
+
if message_too_big?(message_json_size)
|
30
|
+
logger.error('a message exceeded the maximum allowed size')
|
31
|
+
else
|
32
|
+
@messages << message
|
33
|
+
@json_size += message_json_size + 1 # One byte for the comma
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def full?
|
38
|
+
item_count_exhausted? || size_exhausted?
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear
|
42
|
+
@messages.clear
|
43
|
+
@json_size = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def_delegators :@messages, :to_json
|
47
|
+
def_delegators :@messages, :empty?
|
48
|
+
def_delegators :@messages, :length
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def item_count_exhausted?
|
53
|
+
@messages.length >= @max_message_count
|
54
|
+
end
|
55
|
+
|
56
|
+
def message_too_big?(message_json_size)
|
57
|
+
message_json_size > Defaults::Message::MAX_BYTES
|
58
|
+
end
|
59
|
+
|
60
|
+
# We consider the max size here as just enough to leave room for one more
|
61
|
+
# message of the largest size possible. This is a shortcut that allows us
|
62
|
+
# to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff
|
63
|
+
# here is that we might fit in less messages than possible into a batch.
|
64
|
+
#
|
65
|
+
# The alternative is to use our own `Queue` implementation that allows
|
66
|
+
# peeking, and to consider the next message size when calculating whether
|
67
|
+
# the message can be accomodated in this batch.
|
68
|
+
def size_exhausted?
|
69
|
+
@json_size >= (MAX_BYTES - Defaults::Message::MAX_BYTES)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Rox
|
2
|
+
module Core
|
3
|
+
class Analytics
|
4
|
+
class Response
|
5
|
+
attr_reader :status, :error
|
6
|
+
|
7
|
+
# public: Simple class to wrap responses from the API
|
8
|
+
#
|
9
|
+
#
|
10
|
+
def initialize(status = 200, error = nil)
|
11
|
+
@status = status
|
12
|
+
@error = error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Rox
|
2
|
+
module Core
|
3
|
+
class Analytics
|
4
|
+
class TestQueue
|
5
|
+
attr_reader :messages
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
reset!
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
all[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def count
|
16
|
+
all.count
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(message)
|
20
|
+
all << message
|
21
|
+
send(message[:type]) << message
|
22
|
+
end
|
23
|
+
|
24
|
+
def alias
|
25
|
+
messages[:alias] ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def all
|
29
|
+
messages[:all] ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def group
|
33
|
+
messages[:group] ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def identify
|
37
|
+
messages[:identify] ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def page
|
41
|
+
messages[:page] ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
def screen
|
45
|
+
messages[:screen] ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
def track
|
49
|
+
messages[:track] ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset!
|
53
|
+
@messages = {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'rox/core/analytics/defaults'
|
2
|
+
require 'rox/core/analytics/utils'
|
3
|
+
require 'rox/core/analytics/response'
|
4
|
+
require 'rox/core/analytics/logging'
|
5
|
+
require 'rox/core/analytics/backoff_policy'
|
6
|
+
require 'net/http'
|
7
|
+
require 'net/https'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
module Rox
|
11
|
+
module Core
|
12
|
+
class Analytics
|
13
|
+
class Transport
|
14
|
+
include Rox::Core::Analytics::Defaults::Request
|
15
|
+
include Rox::Core::Analytics::Utils
|
16
|
+
include Rox::Core::Analytics::Logging
|
17
|
+
|
18
|
+
def initialize(device_properties)
|
19
|
+
@device_properties = device_properties
|
20
|
+
uri = URI.parse(Rox::Core::Environment.analytics_path)
|
21
|
+
logger.debug("Using Analytics url #{uri}")
|
22
|
+
@headers = {
|
23
|
+
'Accept' => 'application/json',
|
24
|
+
'Content-Type' => 'application/json',
|
25
|
+
'User-Agent' => "ruby/#{device_properties.lib_version}"
|
26
|
+
}
|
27
|
+
@path = uri.path + '/impression/' + device_properties.rollout_key
|
28
|
+
@retries = RETRIES
|
29
|
+
@backoff_policy = Rox::Core::Analytics::BackoffPolicy.new
|
30
|
+
|
31
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
32
|
+
http.use_ssl = uri.scheme == 'https'
|
33
|
+
http.read_timeout = 8
|
34
|
+
http.open_timeout = 4
|
35
|
+
|
36
|
+
@http = http
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sends a batch of messages to the API
|
40
|
+
#
|
41
|
+
# @return [Response] API response
|
42
|
+
def send(batch)
|
43
|
+
logger.debug("Sending request for #{batch.length} items")
|
44
|
+
|
45
|
+
last_response, exception = retry_with_backoff(@retries) do
|
46
|
+
status_code, body = send_request(batch)
|
47
|
+
should_retry = should_retry_request?(status_code, body)
|
48
|
+
logger.debug("Response status code: #{status_code}")
|
49
|
+
logger.debug("Response error: #{body}") if status_code != 200
|
50
|
+
|
51
|
+
[Response.new(status_code, body), should_retry]
|
52
|
+
end
|
53
|
+
|
54
|
+
if exception
|
55
|
+
logger.error(exception.message)
|
56
|
+
exception.backtrace.each { |line| logger.error(line) }
|
57
|
+
Response.new(-1, exception.to_s)
|
58
|
+
else
|
59
|
+
last_response
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Closes a persistent connection if it exists
|
64
|
+
def shutdown
|
65
|
+
@http.finish if @http.started?
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def should_retry_request?(status_code, body)
|
71
|
+
if status_code >= 500
|
72
|
+
true # Server error
|
73
|
+
elsif status_code == 429
|
74
|
+
true # Rate limited
|
75
|
+
elsif status_code >= 400
|
76
|
+
logger.error(body)
|
77
|
+
false # Client error. Do not retry, but log
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Takes a block that returns [result, should_retry].
|
84
|
+
#
|
85
|
+
# Retries upto `retries_remaining` times, if `should_retry` is false or
|
86
|
+
# an exception is raised. `@backoff_policy` is used to determine the
|
87
|
+
# duration to sleep between attempts
|
88
|
+
#
|
89
|
+
# Returns [last_result, raised_exception]
|
90
|
+
def retry_with_backoff(retries_remaining, &block)
|
91
|
+
result, caught_exception = nil
|
92
|
+
should_retry = false
|
93
|
+
|
94
|
+
begin
|
95
|
+
result, should_retry = yield
|
96
|
+
return [result, nil] unless should_retry
|
97
|
+
rescue StandardError => e
|
98
|
+
should_retry = true
|
99
|
+
caught_exception = e
|
100
|
+
end
|
101
|
+
|
102
|
+
if should_retry && (retries_remaining > 1)
|
103
|
+
logger.debug("Retrying request, #{retries_remaining} retries left")
|
104
|
+
sleep(@backoff_policy.next_interval.to_f / 1000)
|
105
|
+
retry_with_backoff(retries_remaining - 1, &block)
|
106
|
+
else
|
107
|
+
[result, caught_exception]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sends a request for the batch, returns [status_code, body]
|
112
|
+
def send_request(batch)
|
113
|
+
payload = JSON.generate(
|
114
|
+
:analyticsVersion => '1.0.0',
|
115
|
+
:sdkVersion => @device_properties.lib_version,
|
116
|
+
:time => DateTime.now.strftime('%Q').to_i,
|
117
|
+
:platform => @device_properties.all_properties[PropertyType::PLATFORM.name],
|
118
|
+
:rolloutKey => @device_properties.rollout_key,
|
119
|
+
:events => batch
|
120
|
+
)
|
121
|
+
request = Net::HTTP::Post.new(@path, @headers)
|
122
|
+
|
123
|
+
if self.class.stub
|
124
|
+
logger.debug "stubbed request to #{@path}: #{JSON.generate(batch)}"
|
125
|
+
|
126
|
+
[200, '{}']
|
127
|
+
else
|
128
|
+
@http.start unless @http.started? # Maintain a persistent connection
|
129
|
+
response = @http.request(request, payload)
|
130
|
+
[response.code.to_i, response.body]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class << self
|
135
|
+
attr_writer :stub
|
136
|
+
|
137
|
+
def stub
|
138
|
+
@stub || ENV['STUB']
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Rox
|
4
|
+
module Core
|
5
|
+
class Analytics
|
6
|
+
module Utils
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# public: Return a new hash with keys converted from strings to symbols
|
10
|
+
#
|
11
|
+
def symbolize_keys(hash)
|
12
|
+
hash.each_with_object({}) do |(k, v), memo|
|
13
|
+
memo[k.to_sym] = v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# public: Convert hash keys from strings to symbols in place
|
18
|
+
#
|
19
|
+
def symbolize_keys!(hash)
|
20
|
+
hash.replace symbolize_keys hash
|
21
|
+
end
|
22
|
+
|
23
|
+
# public: Return a new hash with keys as strings
|
24
|
+
#
|
25
|
+
def stringify_keys(hash)
|
26
|
+
hash.each_with_object({}) do |(k, v), memo|
|
27
|
+
memo[k.to_s] = v
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# public: Returns a new hash with all the date values in the into iso8601
|
32
|
+
# strings
|
33
|
+
#
|
34
|
+
def isoify_dates(hash)
|
35
|
+
hash.each_with_object({}) do |(k, v), memo|
|
36
|
+
memo[k] = datetime_in_iso8601(v)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# public: Converts all the date values in the into iso8601 strings in place
|
41
|
+
#
|
42
|
+
def isoify_dates!(hash)
|
43
|
+
hash.replace isoify_dates hash
|
44
|
+
end
|
45
|
+
|
46
|
+
# public: Returns a uid string
|
47
|
+
#
|
48
|
+
def uid
|
49
|
+
arr = SecureRandom.random_bytes(16).unpack('NnnnnN')
|
50
|
+
arr[2] = (arr[2] & 0x0fff) | 0x4000
|
51
|
+
arr[3] = (arr[3] & 0x3fff) | 0x8000
|
52
|
+
'%08x-%04x-%04x-%04x-%04x%08x' % arr
|
53
|
+
end
|
54
|
+
|
55
|
+
def datetime_in_iso8601(datetime)
|
56
|
+
case datetime
|
57
|
+
when Time
|
58
|
+
time_in_iso8601 datetime
|
59
|
+
when DateTime
|
60
|
+
time_in_iso8601 datetime.to_time
|
61
|
+
when Date
|
62
|
+
date_in_iso8601 datetime
|
63
|
+
else
|
64
|
+
datetime
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def time_in_iso8601(time)
|
69
|
+
"#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def date_in_iso8601(date)
|
73
|
+
date.strftime('%F')
|
74
|
+
end
|
75
|
+
|
76
|
+
def formatted_offset(time, colon = true, alternate_utc_string = nil)
|
77
|
+
time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon)
|
78
|
+
end
|
79
|
+
|
80
|
+
def seconds_to_utc_offset(seconds, colon = true)
|
81
|
+
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
|
82
|
+
end
|
83
|
+
|
84
|
+
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
|
85
|
+
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rox/core/analytics/defaults'
|
2
|
+
require 'rox/core/analytics/message_batch'
|
3
|
+
require 'rox/core/analytics/transport'
|
4
|
+
require 'rox/core/analytics/utils'
|
5
|
+
|
6
|
+
module Rox
|
7
|
+
module Core
|
8
|
+
class Analytics
|
9
|
+
class Worker
|
10
|
+
include Rox::Core::Analytics::Utils
|
11
|
+
include Rox::Core::Analytics::Defaults
|
12
|
+
include Rox::Core::Analytics::Logging
|
13
|
+
|
14
|
+
# public: Creates a new worker
|
15
|
+
#
|
16
|
+
# The worker continuously takes messages off the queue
|
17
|
+
# and makes requests to the segment.io api
|
18
|
+
#
|
19
|
+
# queue - Queue synchronized between client and worker
|
20
|
+
# @param [Rox::Core::DeviceProperties] device_properties
|
21
|
+
#
|
22
|
+
def initialize(queue, device_properties)
|
23
|
+
@queue = queue
|
24
|
+
@device_properties = device_properties
|
25
|
+
@on_error = proc { |status, error| }
|
26
|
+
batch_size = Defaults::MessageBatch::MAX_SIZE
|
27
|
+
@batch = MessageBatch.new(batch_size)
|
28
|
+
@lock = Mutex.new
|
29
|
+
@transport = Transport.new(device_properties)
|
30
|
+
end
|
31
|
+
|
32
|
+
# public: Continuously runs the loop to check for new events
|
33
|
+
#
|
34
|
+
def run
|
35
|
+
until Thread.current[:should_exit]
|
36
|
+
return if @queue.empty?
|
37
|
+
|
38
|
+
@lock.synchronize do
|
39
|
+
consume_message_from_queue! until @batch.full? || @queue.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
res = @transport.send @batch
|
43
|
+
@on_error.call(res.status, res.error) unless res.status == 200
|
44
|
+
|
45
|
+
@lock.synchronize { @batch.clear }
|
46
|
+
end
|
47
|
+
ensure
|
48
|
+
@transport.shutdown
|
49
|
+
end
|
50
|
+
|
51
|
+
# public: Check whether we have outstanding requests.
|
52
|
+
#
|
53
|
+
def is_requesting?
|
54
|
+
@lock.synchronize { !@batch.empty? }
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def consume_message_from_queue!
|
60
|
+
@batch << @queue.pop
|
61
|
+
rescue MessageBatch::JSONGenerationError => e
|
62
|
+
@on_error.call(-1, e.to_s)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,77 +1,85 @@
|
|
1
1
|
module Rox
|
2
2
|
module Core
|
3
3
|
module Environment
|
4
|
+
def self.reset(rox_options = nil)
|
5
|
+
@roxy_internal_path = 'device/request_configuration'
|
6
|
+
if !rox_options&.network_configurations_options.nil?
|
7
|
+
@cdn_path = rox_options&.network_configurations_options&.get_config_cloud_endpoint
|
8
|
+
@api_path = rox_options&.network_configurations_options&.get_config_api_endpoint
|
9
|
+
@state_cdn_path = rox_options&.network_configurations_options&.send_state_cloud_endpoint
|
10
|
+
@state_api_path = rox_options&.network_configurations_options&.send_state_api_endpoint
|
11
|
+
@analytics_path = rox_options&.network_configurations_options&.analytics_endpoint
|
12
|
+
@notifications_path = rox_options&.network_configurations_options&.push_notification_endpoint
|
13
|
+
else
|
14
|
+
case ENV['ROLLOUT_MODE']
|
15
|
+
when 'QA'
|
16
|
+
setQA
|
17
|
+
when 'LOCAL'
|
18
|
+
setLOCAL
|
19
|
+
else
|
20
|
+
setDefault(rox_options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
4
25
|
def self.roxy_internal_path
|
5
|
-
|
26
|
+
@roxy_internal_path
|
6
27
|
end
|
7
28
|
|
8
29
|
def self.cdn_path
|
9
|
-
|
10
|
-
when 'QA'
|
11
|
-
'https://qa-conf.rollout.io'
|
12
|
-
when 'LOCAL'
|
13
|
-
'https://development-conf.rollout.io'
|
14
|
-
else
|
15
|
-
'https://conf.rollout.io'
|
16
|
-
end
|
30
|
+
@cdn_path
|
17
31
|
end
|
18
32
|
|
19
|
-
def self.api_path
|
20
|
-
|
21
|
-
when 'QA'
|
22
|
-
'https://qa-api.rollout.io/device/get_configuration'
|
23
|
-
when 'LOCAL'
|
24
|
-
'http://127.0.0.1:8557/device/get_configuration'
|
25
|
-
else
|
26
|
-
api_url ||= 'https://x-api.rollout.io'
|
27
|
-
"#{api_url}/device/get_configuration"
|
28
|
-
end
|
33
|
+
def self.api_path
|
34
|
+
@api_path
|
29
35
|
end
|
30
36
|
|
31
37
|
def self.state_cdn_path
|
32
|
-
|
33
|
-
when 'QA'
|
34
|
-
'https://qa-statestore.rollout.io'
|
35
|
-
when 'LOCAL'
|
36
|
-
'https://development-statestore.rollout.io'
|
37
|
-
else
|
38
|
-
'https://statestore.rollout.io'
|
39
|
-
end
|
38
|
+
@state_cdn_path
|
40
39
|
end
|
41
40
|
|
42
|
-
def self.state_api_path
|
43
|
-
|
44
|
-
when 'QA'
|
45
|
-
'https://qa-api.rollout.io/device/update_state_store'
|
46
|
-
when 'LOCAL'
|
47
|
-
'http://127.0.0.1:8557/device/update_state_store'
|
48
|
-
else
|
49
|
-
api_url ||= 'https://x-api.rollout.io'
|
50
|
-
"#{api_url}/device/update_state_store"
|
51
|
-
end
|
41
|
+
def self.state_api_path
|
42
|
+
@state_api_path
|
52
43
|
end
|
53
44
|
|
54
|
-
def self.analytics_path
|
55
|
-
|
56
|
-
when 'QA'
|
57
|
-
'https://qaanalytic.rollout.io'
|
58
|
-
when 'LOCAL'
|
59
|
-
'http://127.0.0.1:8787'
|
60
|
-
else
|
61
|
-
analytics_url
|
62
|
-
end
|
45
|
+
def self.analytics_path
|
46
|
+
@analytics_path
|
63
47
|
end
|
64
48
|
|
65
49
|
def self.notifications_path
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
50
|
+
@notifications_path
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def self.setQA()
|
55
|
+
@cdn_path = 'https://qa-conf.rollout.io'
|
56
|
+
@api_path = 'https://qa-api.rollout.io/device/get_configuration'
|
57
|
+
@state_cdn_path = 'https://qa-statestore.rollout.io'
|
58
|
+
@state_api_path = 'https://qa-api.rollout.io/device/update_state_store'
|
59
|
+
@analytics_path = 'https://qaanalytic.rollout.io'
|
60
|
+
@notifications_path = 'https://qax-push.rollout.io/sse'
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.setLOCAL()
|
64
|
+
@cdn_path = 'https://development-conf.rollout.io'
|
65
|
+
@api_path = 'http://127.0.0.1:8557/device/get_configuration'
|
66
|
+
@state_cdn_path = 'https://development-statestore.rollout.io'
|
67
|
+
@state_api_path = 'http://127.0.0.1:8557/device/update_state_store'
|
68
|
+
@analytics_path = 'http://127.0.0.1:8787'
|
69
|
+
@notifications_path = 'http://127.0.0.1:8887/sse'
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.setDefault(rox_options)
|
73
|
+
@cdn_path = 'https://conf.rollout.io'
|
74
|
+
@state_cdn_path = 'https://statestore.rollout.io'
|
75
|
+
alternative_api_url = rox_options&.self_managed_options&.server_url || 'https://x-api.rollout.io'
|
76
|
+
@api_path = "#{alternative_api_url}/device/get_configuration"
|
77
|
+
@state_api_path = "#{alternative_api_url}/device/update_state_store"
|
78
|
+
@analytics_path = rox_options&.self_managed_options&.analytics_url ||'https://analytic.rollout.io'
|
79
|
+
@notifications_path = 'https://push.rollout.io/sse'
|
74
80
|
end
|
75
81
|
end
|
76
82
|
end
|
77
83
|
end
|
84
|
+
|
85
|
+
Rox::Core::Environment.reset
|
data/lib/rox/core/core.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rox/core/analytics/client'
|
1
2
|
require 'rox/core/repositories/flag_repository'
|
2
3
|
require 'rox/core/repositories/custom_property_repository'
|
3
4
|
require 'rox/core/repositories/target_group_repository'
|
@@ -48,6 +49,7 @@ module Rox
|
|
48
49
|
@last_configurations = nil
|
49
50
|
@internal_flags = nil
|
50
51
|
@push_updates_listener = nil
|
52
|
+
@analytics_client = nil
|
51
53
|
end
|
52
54
|
|
53
55
|
def userspace_unhandled_error_handler=(handler)
|
@@ -57,6 +59,7 @@ module Rox
|
|
57
59
|
def setup(sdk_settings, device_properties)
|
58
60
|
@sdk_settings = sdk_settings
|
59
61
|
@rox_options = device_properties.rox_options
|
62
|
+
Rox::Core::Environment.reset(@rox_options)
|
60
63
|
|
61
64
|
experiments_extensions = ExperimentsExtensions.new(@parser, @target_group_repository, @flag_repository,
|
62
65
|
@experiment_repository)
|
@@ -68,12 +71,14 @@ module Rox
|
|
68
71
|
|
69
72
|
validate_api_key(sdk_settings&.api_key) if roxy_path.nil?
|
70
73
|
|
71
|
-
# TODO: Analytics.Analytics.Initialize(deviceProperties.RolloutKey, deviceProperties)
|
72
74
|
@internal_flags = InternalFlags.new(@experiment_repository, @parser, @rox_options)
|
73
75
|
|
74
|
-
|
76
|
+
if roxy_path.nil?
|
77
|
+
@analytics_client = create_analytics_client(device_properties)
|
78
|
+
end
|
79
|
+
|
75
80
|
@impression_invoker = ImpressionInvoker.new(@internal_flags, @custom_property_repository, device_properties,
|
76
|
-
|
81
|
+
@analytics_client, !roxy_path.nil?, @user_unhandled_error_invoker)
|
77
82
|
@flag_setter = FlagSetter.new(@flag_repository, @parser, @experiment_repository, @impression_invoker)
|
78
83
|
buid = BUID.new(sdk_settings, device_properties, @flag_repository, @custom_property_repository)
|
79
84
|
|
@@ -103,14 +108,13 @@ module Rox
|
|
103
108
|
@configuration_fetched_invoker)
|
104
109
|
end
|
105
110
|
|
106
|
-
configuration_fetched_handler = nil
|
107
|
-
configuration_fetched_handler = @rox_options.configuration_fetched_handler unless @rox_options.nil?
|
108
|
-
|
109
111
|
@configuration_fetched_invoker.register_start_stop_push(proc do |args|
|
110
112
|
start_or_stop_push_updated_listener unless args.fetcher_status == FetcherStatus::ERROR_FETCHED_FAILED
|
111
113
|
end)
|
112
114
|
|
113
|
-
|
115
|
+
if !@rox_options.nil? && !@rox_options.configuration_fetched_handler.nil?
|
116
|
+
@configuration_fetched_invoker.register_fetched_handler(&@rox_options.configuration_fetched_handler)
|
117
|
+
end
|
114
118
|
|
115
119
|
@thread = Thread.new do
|
116
120
|
Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception)
|
@@ -138,9 +142,9 @@ module Rox
|
|
138
142
|
@push_updates_listener = nil
|
139
143
|
end
|
140
144
|
|
141
|
-
|
145
|
+
return if @analytics_client.nil?
|
142
146
|
|
143
|
-
|
147
|
+
@analytics_client.flush
|
144
148
|
end
|
145
149
|
|
146
150
|
def fetch
|
@@ -224,6 +228,12 @@ module Rox
|
|
224
228
|
raise ArgumentError, 'Illegal Rollout api key'
|
225
229
|
end
|
226
230
|
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def create_analytics_client(device_properties)
|
235
|
+
Rox::Core::Analytics::Client.new(device_properties)
|
236
|
+
end
|
227
237
|
end
|
228
238
|
end
|
229
239
|
end
|
@@ -26,7 +26,9 @@ module Rox
|
|
26
26
|
|
27
27
|
def value(context = nil)
|
28
28
|
merged_context = MergedContext.new(@parser&.global_context, context)
|
29
|
-
internal_value(merged_context, false)
|
29
|
+
return_value = internal_value(merged_context, false)
|
30
|
+
send_impressions(return_value, merged_context)
|
31
|
+
return_value
|
30
32
|
end
|
31
33
|
|
32
34
|
def internal_enabled?(context, nil_instead_of_default = false)
|
@@ -4,7 +4,9 @@ module Rox
|
|
4
4
|
module Core
|
5
5
|
class RoxDouble < RoxString
|
6
6
|
def value(context = nil)
|
7
|
-
internal_value(context, false, Float)
|
7
|
+
return_value = Rox::Server::NormalizeFlagType.normalize_float(internal_value(context, false, Float))
|
8
|
+
send_impressions(return_value, context)
|
9
|
+
return_value
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|