rox-rollout 5.0.0 → 5.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/.github/workflows/ruby.yml +2 -1
- data/.gitignore +1 -0
- data/e2e/rox_e2e_test.rb +63 -18
- data/e2e/test_vars.rb +2 -1
- data/e2e-server/.gitignore +1 -0
- 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 +143 -0
- data/lib/rox/core/analytics/utils.rb +89 -0
- data/lib/rox/core/analytics/worker.rb +67 -0
- data/lib/rox/core/configuration/configuration_fetched_invoker.rb +10 -1
- data/lib/rox/core/core.rb +17 -10
- 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/error_handling/userspace_handler_exception.rb +3 -2
- data/lib/rox/core/impression/impression_invoker.rb +13 -9
- data/lib/rox/core/network/state_sender.rb +2 -1
- data/lib/rox/core/properties/custom_property.rb +1 -1
- data/lib/rox/core/repositories/roxx/properties_extensions.rb +2 -2
- data/lib/rox/core/roxx/parser.rb +1 -1
- data/lib/rox/server/rox_server.rb +4 -0
- data/lib/rox/version.rb +1 -1
- data/rox.gemspec +1 -3
- metadata +20 -24
- data/lib/rox/core/analytics.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17c6afc3c1c54a584ecec20f9213d1bf3d90fa603e4d3f378fd4014fafe7fbbe
|
4
|
+
data.tar.gz: cc43890c658daf69fa6e3307ea00a78a6e6d16d94e7f1efc1f7aa8ede5466b2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 715c0071fb927d9f8947e47135e1c60036a19ac88ead19d2d8c482821291e04acb4c0adc57027b5e7a6ec305718c68ef64ffe2a5238835045855daf48d1a3075
|
7
|
+
data.tar.gz: 2c56942cdf66b5a809970fa9ebabaa20b09e0fedf4a42f824234af9618eab0dd41be7b3c557fe3c597fa3175b35b29440cd74a4f8261d0ef0ffa91d1af651aee
|
data/.github/workflows/ruby.yml
CHANGED
@@ -8,13 +8,14 @@ jobs:
|
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
10
|
os: [ubuntu-latest]
|
11
|
-
ruby: [2.4, 2.5, 2.6, 2.7, 3.0]
|
11
|
+
ruby: [2.4, 2.5, 2.6, 2.7, '3.0', 3.1]
|
12
12
|
runs-on: ${{ matrix.os }}
|
13
13
|
steps:
|
14
14
|
- uses: actions/checkout@v2
|
15
15
|
- uses: ruby/setup-ruby@v1
|
16
16
|
with:
|
17
17
|
ruby-version: ${{ matrix.ruby }}
|
18
|
+
bundler: 2.3.3
|
18
19
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
19
20
|
- run: bundle exec rake test
|
20
21
|
- run: bundle exec rake e2e
|
data/.gitignore
CHANGED
data/e2e/rox_e2e_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'rox/server/rox_options'
|
3
3
|
require 'rox/server/rox_server'
|
4
|
+
require 'rox/server/logging/server_logger'
|
4
5
|
require_relative 'container'
|
5
6
|
require_relative 'custom_props'
|
6
7
|
require_relative 'test_vars'
|
@@ -23,30 +24,58 @@ module E2E
|
|
23
24
|
class RoxE2ETest < Minitest::Test
|
24
25
|
ENV['ROLLOUT_MODE'] = 'QA'
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
@@setupComplete = false
|
28
|
+
@@testsRun = 0
|
29
|
+
|
30
|
+
def setup
|
31
|
+
@@testsRun += 1
|
32
|
+
if !@@setupComplete
|
33
|
+
|
34
|
+
configuration_fetched_handler = proc do |e|
|
35
|
+
if !e.nil? && e.fetcher_status == Rox::Core::FetcherStatus::APPLIED_FROM_NETWORK
|
36
|
+
TestVars.configuration_fetched_count += 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
impression_handler = proc do |e|
|
41
|
+
puts "targeting: #{e.reporting_value.targeting}"
|
42
|
+
if e.reporting_value.targeting
|
43
|
+
puts "#{Time.now}: flag #{e.reporting_value.name} value is #{e.reporting_value.value}"
|
44
|
+
else
|
45
|
+
puts "#{Time.now}: flag #{e.reporting_value.name} has no conditions, or targeting is off. default value #{!!e.reporting_value.value == e.reporting_value.value ? (e.reporting_value.value ? 'true' : 'false') : e.reporting_value.value} was used"
|
46
|
+
end
|
47
|
+
if !e.nil? && !e.reporting_value.nil? && (e.reporting_value.name == 'flag_for_impression')
|
48
|
+
TestVars.is_impression_raised = true
|
49
|
+
TestVars.impression_raised_times += 1
|
50
|
+
end
|
51
|
+
TestVars.impression_returned_args = e
|
52
|
+
end
|
53
|
+
|
54
|
+
option = Rox::Server::RoxOptions.new(
|
55
|
+
configuration_fetched_handler: configuration_fetched_handler,
|
56
|
+
impression_handler: impression_handler,
|
57
|
+
dev_mode_key: '67e39e708444aa953414e444',
|
58
|
+
logger: Rox::Server::ServerLogger.new
|
59
|
+
)
|
60
|
+
|
61
|
+
@@container = Container.new
|
62
|
+
Rox::Server::RoxServer.register(@@container)
|
63
|
+
CustomProps.create_custom_props
|
64
|
+
Rox::Server::RoxServer.setup('5b82864ebc3aec37aff1fdd5', option).join
|
65
|
+
@@setupComplete = true
|
29
66
|
end
|
30
67
|
end
|
31
68
|
|
32
|
-
|
33
|
-
if
|
34
|
-
|
69
|
+
def teardown
|
70
|
+
if @@testsRun == RoxE2ETest.runnable_methods.length
|
71
|
+
Rox::Server::RoxServer.shutdown
|
35
72
|
end
|
36
|
-
TestVars.impression_returned_args = e
|
37
73
|
end
|
38
74
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
logger: Logger.new
|
44
|
-
)
|
45
|
-
|
46
|
-
@@container = Container.new
|
47
|
-
Rox::Server::RoxServer.register(@@container)
|
48
|
-
CustomProps.create_custom_props
|
49
|
-
Rox::Server::RoxServer.setup('5b82864ebc3aec37aff1fdd5', option).join
|
75
|
+
def before_setup
|
76
|
+
TestVars.is_impression_raised = false
|
77
|
+
TestVars.impression_raised_times = 0
|
78
|
+
end
|
50
79
|
|
51
80
|
def test_simple_flag
|
52
81
|
assert_equal true, @@container.simple_flag.enabled?
|
@@ -135,6 +164,22 @@ module E2E
|
|
135
164
|
assert_equal 'val', TestVars.impression_returned_args.context['var']
|
136
165
|
end
|
137
166
|
|
167
|
+
def test_static_flag_impression_raised_once
|
168
|
+
assert_equal false, TestVars.is_impression_raised
|
169
|
+
assert_equal 0, TestVars.impression_raised_times
|
170
|
+
@@container.flag_for_impression.enabled?
|
171
|
+
assert_equal true, TestVars.is_impression_raised
|
172
|
+
assert_equal 1, TestVars.impression_raised_times
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_dynamic_flag_impression_raised_once
|
176
|
+
assert_equal false, TestVars.is_impression_raised
|
177
|
+
assert_equal 0, TestVars.impression_raised_times
|
178
|
+
Rox::Server::RoxServer::dynamic_api.enabled?('flag_for_impression', true)
|
179
|
+
assert_equal true, TestVars.is_impression_raised
|
180
|
+
assert_equal 1, TestVars.impression_raised_times
|
181
|
+
end
|
182
|
+
|
138
183
|
def test_flag_dependency
|
139
184
|
TestVars.is_prop_for_target_group_for_dependency = true
|
140
185
|
assert_equal true, @@container.flag_for_dependency.enabled?
|
data/e2e/test_vars.rb
CHANGED
@@ -2,7 +2,7 @@ module E2E
|
|
2
2
|
module TestVars
|
3
3
|
class << self
|
4
4
|
attr_accessor :is_computed_boolean_prop_called, :is_computed_string_prop_called, :is_computed_int_prop_called,
|
5
|
-
:is_computed_float_prop_called, :is_computed_semver_prop_called, :target_group1, :target_group2, :is_impression_raised, :is_prop_for_target_group_for_dependency, :configuration_fetched_count, :impression_returned_args
|
5
|
+
:is_computed_float_prop_called, :is_computed_semver_prop_called, :target_group1, :target_group2, :is_impression_raised, :impression_raised_times, :is_prop_for_target_group_for_dependency, :configuration_fetched_count, :impression_returned_args
|
6
6
|
end
|
7
7
|
|
8
8
|
@is_computed_boolean_prop_called = false
|
@@ -13,6 +13,7 @@ module E2E
|
|
13
13
|
@target_group1 = false
|
14
14
|
@target_group2 = false
|
15
15
|
@is_impression_raised = false
|
16
|
+
@impression_raised_times = 0
|
16
17
|
@is_prop_for_target_group_for_dependency = false
|
17
18
|
|
18
19
|
@configuration_fetched_count = 0
|
@@ -0,0 +1 @@
|
|
1
|
+
*.out
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rox/core/analytics/defaults'
|
2
|
+
|
3
|
+
module Rox
|
4
|
+
module Core
|
5
|
+
class Analytics
|
6
|
+
class BackoffPolicy
|
7
|
+
include Rox::Core::Analytics::Defaults::BackoffPolicy
|
8
|
+
|
9
|
+
# @param [Hash] opts
|
10
|
+
# @option opts [Numeric] :min_timeout_ms The minimum backoff timeout
|
11
|
+
# @option opts [Numeric] :max_timeout_ms The maximum backoff timeout
|
12
|
+
# @option opts [Numeric] :multiplier The value to multiply the current
|
13
|
+
# interval with for each retry attempt
|
14
|
+
# @option opts [Numeric] :randomization_factor The randomization factor
|
15
|
+
# to use to create a range around the retry interval
|
16
|
+
def initialize(opts = {})
|
17
|
+
@min_timeout_ms = opts[:min_timeout_ms] || MIN_TIMEOUT_MS
|
18
|
+
@max_timeout_ms = opts[:max_timeout_ms] || MAX_TIMEOUT_MS
|
19
|
+
@multiplier = opts[:multiplier] || MULTIPLIER
|
20
|
+
@randomization_factor = opts[:randomization_factor] || RANDOMIZATION_FACTOR
|
21
|
+
|
22
|
+
@attempts = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Numeric] the next backoff interval, in milliseconds.
|
26
|
+
def next_interval
|
27
|
+
interval = @min_timeout_ms * (@multiplier ** @attempts)
|
28
|
+
interval = add_jitter(interval, @randomization_factor)
|
29
|
+
|
30
|
+
@attempts += 1
|
31
|
+
|
32
|
+
[interval, @max_timeout_ms].min
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def add_jitter(base, randomization_factor)
|
38
|
+
random_number = rand
|
39
|
+
max_deviation = base * randomization_factor
|
40
|
+
deviation = random_number * max_deviation
|
41
|
+
|
42
|
+
if random_number < 0.5
|
43
|
+
base - deviation
|
44
|
+
else
|
45
|
+
base + deviation
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'time'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'rox/core/analytics/defaults'
|
6
|
+
require 'rox/core/analytics/logging'
|
7
|
+
require 'rox/core/analytics/utils'
|
8
|
+
require 'rox/core/analytics/worker'
|
9
|
+
|
10
|
+
module Rox
|
11
|
+
module Core
|
12
|
+
class Analytics
|
13
|
+
class Client
|
14
|
+
include Rox::Core::Analytics::Utils
|
15
|
+
include Rox::Core::Analytics::Logging
|
16
|
+
|
17
|
+
# @param [Rox::Core::DeviceProperties] device_properties
|
18
|
+
def initialize(device_properties)
|
19
|
+
@queue = Queue.new
|
20
|
+
@max_queue_size = Defaults::Queue::MAX_SIZE
|
21
|
+
@worker_mutex = Mutex.new
|
22
|
+
@worker = Worker.new(@queue, device_properties)
|
23
|
+
@worker_thread = nil
|
24
|
+
|
25
|
+
at_exit { @worker_thread && @worker_thread[:should_exit] = true }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Synchronously waits until the worker has flushed the queue.
|
29
|
+
#
|
30
|
+
# Use only for scripts which are not long-running, and will specifically
|
31
|
+
# exit
|
32
|
+
def flush
|
33
|
+
while !@queue.empty? || @worker.is_requesting?
|
34
|
+
ensure_worker_running
|
35
|
+
sleep(0.1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!macro common_attrs
|
40
|
+
# @option attrs [String] :anonymous_id ID for a user when you don't know
|
41
|
+
# who they are yet. (optional but you must provide either an
|
42
|
+
# `anonymous_id` or `user_id`)
|
43
|
+
# @option attrs [Hash] :context ({})
|
44
|
+
# @option attrs [Hash] :integrations What integrations this event
|
45
|
+
# goes to (optional)
|
46
|
+
# @option attrs [String] :message_id ID that uniquely
|
47
|
+
# identifies a message across the API. (optional)
|
48
|
+
# @option attrs [Time] :timestamp When the event occurred (optional)
|
49
|
+
# @option attrs [String] :user_id The ID for this user in your database
|
50
|
+
# (optional but you must provide either an `anonymous_id` or `user_id`)
|
51
|
+
# @option attrs [Hash] :options Options such as user traits (optional)
|
52
|
+
|
53
|
+
# Tracks an event
|
54
|
+
#
|
55
|
+
# @see https://segment.com/docs/sources/server/ruby/#track
|
56
|
+
#
|
57
|
+
# @param [Hash] attrs
|
58
|
+
#
|
59
|
+
# @option attrs [String] :event Event name
|
60
|
+
# @option attrs [Hash] :properties Event properties (optional)
|
61
|
+
# @macro common_attrs
|
62
|
+
def track(attrs)
|
63
|
+
symbolize_keys! attrs
|
64
|
+
enqueue(attrs)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Identifies a user
|
68
|
+
#
|
69
|
+
# @see https://segment.com/docs/sources/server/ruby/#identify
|
70
|
+
#
|
71
|
+
# @param [Hash] attrs
|
72
|
+
#
|
73
|
+
# @option attrs [Hash] :traits User traits (optional)
|
74
|
+
# @macro common_attrs
|
75
|
+
def identify(attrs)
|
76
|
+
symbolize_keys! attrs
|
77
|
+
enqueue(FieldParser.parse_for_identify(attrs))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Aliases a user from one id to another
|
81
|
+
#
|
82
|
+
# @see https://segment.com/docs/sources/server/ruby/#alias
|
83
|
+
#
|
84
|
+
# @param [Hash] attrs
|
85
|
+
#
|
86
|
+
# @option attrs [String] :previous_id The ID to alias from
|
87
|
+
# @macro common_attrs
|
88
|
+
def alias(attrs)
|
89
|
+
symbolize_keys! attrs
|
90
|
+
enqueue(FieldParser.parse_for_alias(attrs))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Associates a user identity with a group.
|
94
|
+
#
|
95
|
+
# @see https://segment.com/docs/sources/server/ruby/#group
|
96
|
+
#
|
97
|
+
# @param [Hash] attrs
|
98
|
+
#
|
99
|
+
# @option attrs [String] :group_id The ID of the group
|
100
|
+
# @option attrs [Hash] :traits User traits (optional)
|
101
|
+
# @macro common_attrs
|
102
|
+
def group(attrs)
|
103
|
+
symbolize_keys! attrs
|
104
|
+
enqueue(FieldParser.parse_for_group(attrs))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Records a page view
|
108
|
+
#
|
109
|
+
# @see https://segment.com/docs/sources/server/ruby/#page
|
110
|
+
#
|
111
|
+
# @param [Hash] attrs
|
112
|
+
#
|
113
|
+
# @option attrs [String] :name Name of the page
|
114
|
+
# @option attrs [Hash] :properties Page properties (optional)
|
115
|
+
# @macro common_attrs
|
116
|
+
def page(attrs)
|
117
|
+
symbolize_keys! attrs
|
118
|
+
enqueue(FieldParser.parse_for_page(attrs))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Records a screen view (for a mobile app)
|
122
|
+
#
|
123
|
+
# @param [Hash] attrs
|
124
|
+
#
|
125
|
+
# @option attrs [String] :name Name of the screen
|
126
|
+
# @option attrs [Hash] :properties Screen properties (optional)
|
127
|
+
# @option attrs [String] :category The screen category (optional)
|
128
|
+
# @macro common_attrs
|
129
|
+
def screen(attrs)
|
130
|
+
symbolize_keys! attrs
|
131
|
+
enqueue(FieldParser.parse_for_screen(attrs))
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [Fixnum] number of messages in the queue
|
135
|
+
def queued_messages
|
136
|
+
@queue.length
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_queue
|
140
|
+
unless @test
|
141
|
+
raise 'Test queue only available when setting :test to true.'
|
142
|
+
end
|
143
|
+
|
144
|
+
@test_queue ||= TestQueue.new
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# private: Enqueues the action.
|
150
|
+
#
|
151
|
+
# returns Boolean of whether the item was added to the queue.
|
152
|
+
def enqueue(action)
|
153
|
+
|
154
|
+
if @test
|
155
|
+
test_queue << action
|
156
|
+
return true
|
157
|
+
end
|
158
|
+
|
159
|
+
while @queue.length >= @max_queue_size
|
160
|
+
# remove the oldest impression,
|
161
|
+
# and then add the new one (otherwise it just rejects the newer one)
|
162
|
+
@queue.pop
|
163
|
+
end
|
164
|
+
|
165
|
+
@queue << action
|
166
|
+
ensure_worker_running
|
167
|
+
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def ensure_worker_running
|
172
|
+
return if worker_running?
|
173
|
+
@worker_mutex.synchronize do
|
174
|
+
return if worker_running?
|
175
|
+
@worker_thread = Thread.new do
|
176
|
+
@worker.run
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def worker_running?
|
182
|
+
@worker_thread && @worker_thread.alive?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rox
|
2
|
+
module Core
|
3
|
+
class Analytics
|
4
|
+
module Defaults
|
5
|
+
module Request
|
6
|
+
RETRIES = 10
|
7
|
+
end
|
8
|
+
|
9
|
+
module Queue
|
10
|
+
MAX_SIZE = 10000
|
11
|
+
end
|
12
|
+
|
13
|
+
module Message
|
14
|
+
MAX_BYTES = 32768 # 32Kb
|
15
|
+
end
|
16
|
+
|
17
|
+
module MessageBatch
|
18
|
+
MAX_BYTES = 512_000 # 500Kb
|
19
|
+
MAX_SIZE = 100
|
20
|
+
end
|
21
|
+
|
22
|
+
module BackoffPolicy
|
23
|
+
MIN_TIMEOUT_MS = 100
|
24
|
+
MAX_TIMEOUT_MS = 10000
|
25
|
+
MULTIPLIER = 1.5
|
26
|
+
RANDOMIZATION_FACTOR = 0.5
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Rox
|
4
|
+
module Core
|
5
|
+
class Analytics
|
6
|
+
# Wraps an existing logger and adds a prefix to all messages
|
7
|
+
class PrefixedLogger
|
8
|
+
def initialize(logger, prefix)
|
9
|
+
@logger = logger
|
10
|
+
@prefix = prefix
|
11
|
+
end
|
12
|
+
|
13
|
+
def debug(msg)
|
14
|
+
@logger.debug("#{@prefix} #{msg}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def info(msg)
|
18
|
+
@logger.info("#{@prefix} #{msg}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def warn(msg)
|
22
|
+
@logger.warn("#{@prefix} #{msg}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def error(msg)
|
26
|
+
@logger.error("#{@prefix} #{msg}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Logging
|
31
|
+
class << self
|
32
|
+
def logger
|
33
|
+
return @logger if @logger
|
34
|
+
|
35
|
+
base_logger = if defined?(Rails)
|
36
|
+
Rails.logger
|
37
|
+
else
|
38
|
+
logger = Logger.new STDOUT
|
39
|
+
logger.progname = 'Rox::Core::Analytics'
|
40
|
+
logger
|
41
|
+
end
|
42
|
+
@logger = PrefixedLogger.new(base_logger, '[analytics-ruby]')
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_writer :logger
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.included(base)
|
49
|
+
class << base
|
50
|
+
def logger
|
51
|
+
Logging.logger
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def logger
|
57
|
+
Logging.logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -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
|