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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae34336f9f49d2ba7a2f77003d3f537eb53bbf4cde40fff04846a4c71d9fe177
|
4
|
+
data.tar.gz: d168174f3d6b72a482b89868d950e4bd8c30c908c0df6d0c11d276aceb8c229d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be4f8985f0289f5becb88a46f13578d83982ad0b5d471c3241dd8127493cdca9d1479eae63bc87e6c13c81092a37ae63e48bedaa40b806020a497a091e0996f2
|
7
|
+
data.tar.gz: f5c134a9692176e0495bbb3805799a439c14a08b5c160a61148c4affdb7e39f2699d1586e7f853e6bdd7fc43103bd9a66d17fc61b93a9b228627a92a7a6217ba
|
@@ -0,0 +1,52 @@
|
|
1
|
+
name: E2E tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
build:
|
13
|
+
|
14
|
+
runs-on: macos-latest
|
15
|
+
|
16
|
+
steps:
|
17
|
+
- uses: actions/checkout@v3
|
18
|
+
with:
|
19
|
+
path: rox-ruby
|
20
|
+
- name: Checkout e2e tests
|
21
|
+
uses: actions/checkout@v3
|
22
|
+
with:
|
23
|
+
repository: rollout/sdk-end-2-end-tests
|
24
|
+
ref: master
|
25
|
+
token: ${{ secrets.E2E_PAT }}
|
26
|
+
path: sdk-end-2-end-tests
|
27
|
+
- name: link driver
|
28
|
+
working-directory: ./sdk-end-2-end-tests/drivers
|
29
|
+
run: ln -s $GITHUB_WORKSPACE/rox-ruby/e2e-server ruby
|
30
|
+
- name: build e2e node driver
|
31
|
+
working-directory: ./sdk-end-2-end-tests/drivers/nodejs
|
32
|
+
run: |
|
33
|
+
yarn install --frozen-lockfile
|
34
|
+
- name: build and run e2e
|
35
|
+
working-directory: ./sdk-end-2-end-tests
|
36
|
+
run: |
|
37
|
+
yarn install --frozen-lockfile
|
38
|
+
QA_E2E_BEARER=$QA_E2E_BEARER API_HOST=https://api.test.rollout.io CD_API_ENDPOINT=https://api.test.rollout.io/device/get_configuration CD_S3_ENDPOINT=https://rox-conf.test.rollout.io/ SS_API_ENDPOINT=https://api.test.rollout.io/device/update_state_store/ SS_S3_ENDPOINT=https://rox-state.test.rollout.io/ CLIENT_DATA_CACHE_KEY=client_data ANALYTICS_ENDPOINT=https://analytic.test.rollout.io/ NOTIFICATIONS_ENDPOINT=https://push.test.rollout.io/sse SDK_LANG=ruby NODE_ENV=container yarn test:env
|
39
|
+
env:
|
40
|
+
QA_E2E_BEARER: ${{ secrets.QA_E2E_BEARER }}
|
41
|
+
- name: Show e2e server driver logs
|
42
|
+
if: ${{ always() }}
|
43
|
+
run: cat ./sdk-end-2-end-tests/drivers/ruby/log_1234.out || echo "no log file"
|
44
|
+
- name: Show e2e server sync driver logs
|
45
|
+
if: ${{ always() }}
|
46
|
+
run: cat ./sdk-end-2-end-tests/drivers/nodejs/log_1233.out || echo "no log file"
|
47
|
+
- name: Show e2e server init ClientData driver logs
|
48
|
+
if: ${{ always() }}
|
49
|
+
run: cat ./sdk-end-2-end-tests/drivers/ruby/log_2234.out || echo "no log file"
|
50
|
+
- name: Show e2e server init ClientData sync driver logs
|
51
|
+
if: ${{ always() }}
|
52
|
+
run: cat ./sdk-end-2-end-tests/drivers/nodejs/log_2233.out || echo "no log file"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
name:
|
1
|
+
name: Unit Tests
|
2
2
|
|
3
3
|
on: [push]
|
4
4
|
|
@@ -8,14 +8,13 @@ jobs:
|
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
10
|
os: [ubuntu-latest]
|
11
|
-
ruby: [2.
|
11
|
+
ruby: [2.6, 2.7, '3.0', 3.1, 3.2]
|
12
12
|
runs-on: ${{ matrix.os }}
|
13
13
|
steps:
|
14
|
-
- uses: actions/checkout@
|
14
|
+
- uses: actions/checkout@v3
|
15
15
|
- uses: ruby/setup-ruby@v1
|
16
16
|
with:
|
17
17
|
ruby-version: ${{ matrix.ruby }}
|
18
|
-
bundler: 2.
|
18
|
+
bundler: 2.4.6
|
19
19
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
20
20
|
- run: bundle exec rake test
|
21
|
-
- run: bundle exec rake e2e
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -8,12 +8,6 @@ Rake::TestTask.new(:test) do |t|
|
|
8
8
|
t.test_files = FileList['test/**/*_test.rb']
|
9
9
|
end
|
10
10
|
|
11
|
-
Rake::TestTask.new(:e2e) do |t|
|
12
|
-
t.libs << 'lib'
|
13
|
-
t.libs << 'e2e'
|
14
|
-
t.test_files = FileList['e2e/**/*_test.rb']
|
15
|
-
end
|
16
|
-
|
17
11
|
RuboCop::RakeTask.new(:lint)
|
18
12
|
|
19
13
|
task default: :test
|
@@ -0,0 +1 @@
|
|
1
|
+
*.out
|
data/e2e-server/run_server.sh
CHANGED
@@ -2,9 +2,13 @@
|
|
2
2
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
3
3
|
cd -P "$DIR"
|
4
4
|
DIR=`pwd`
|
5
|
-
|
6
|
-
gem install
|
7
|
-
gem install
|
5
|
+
|
6
|
+
# --conservative will save us some time (hopefully gem install just once)
|
7
|
+
gem install sinatra --conservative
|
8
|
+
gem install sinatra-contrib --conservative
|
9
|
+
gem install json --conservative
|
10
|
+
gem install em-eventsource --conservative
|
11
|
+
|
8
12
|
nohup ruby ./server.rb $1 1>"$DIR"/log_"$1".out 2>&1 &
|
9
13
|
while true ; do
|
10
14
|
curl -p http://127.0.0.1:$1/status-check && exit
|
data/e2e-server/server.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
+
# comment the following line and install a rox-ruby gem to test a released version
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
3
|
+
|
1
4
|
require 'sinatra'
|
2
5
|
require 'sinatra/namespace'
|
3
6
|
require 'json'
|
4
7
|
require 'rox/server/flags/rox_flag'
|
5
8
|
require 'rox/server/rox_server'
|
6
9
|
require 'rox/server/rox_options'
|
10
|
+
require 'rox/server/network_configurations_options'
|
7
11
|
|
8
12
|
class Container
|
9
|
-
|
10
|
-
attr_reader :bool_default_false, :bool_default_true
|
13
|
+
attr_reader :boolDefaultFalse, :boolDefaultTrue
|
11
14
|
|
12
15
|
def initialize
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@first_flag = Rox::Server::RoxFlag.new(true)
|
16
|
+
@boolDefaultFalse = Rox::Server::RoxFlag.new(false)
|
17
|
+
@boolDefaultTrue = Rox::Server::RoxFlag.new(true)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
@@ -58,95 +60,63 @@ namespace '/' do
|
|
58
60
|
[200, '']
|
59
61
|
end
|
60
62
|
|
61
|
-
get 'api/values/:id' do |id|
|
62
|
-
if container.first_flag.enabled?
|
63
|
-
[200, JSON.generate({ value: id })]
|
64
|
-
else
|
65
|
-
[200, 'Eladddddd']
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
put 'api/values/:id' do
|
70
|
-
[200, '']
|
71
|
-
end
|
72
|
-
|
73
|
-
delete 'api/values/:id' do
|
74
|
-
[200, '']
|
75
|
-
end
|
76
|
-
|
77
63
|
post '' do
|
78
64
|
data = JSON.parse(request.body.read)
|
79
65
|
action = data['action']
|
80
66
|
payload = data['payload']
|
81
|
-
puts data
|
82
|
-
|
67
|
+
puts "data => #{data}"
|
83
68
|
case action
|
84
69
|
when 'staticFlagIsEnabled'
|
85
70
|
flag = payload['flag']
|
86
71
|
result = container.send(flag.to_sym).enabled?(payload['context'])
|
87
72
|
[200, JSON.generate({ result: result })]
|
88
73
|
when 'registerStaticContainers'
|
89
|
-
Rox::Server::RoxServer.
|
74
|
+
Rox::Server::RoxServer.register_with_namespace('namespace', container)
|
90
75
|
[200, JSON.generate({ result: 'done' })]
|
91
76
|
when 'setCustomPropertyToThrow'
|
92
77
|
def raise_(ex)
|
93
78
|
raise ex
|
94
79
|
end
|
95
|
-
Rox::Server::RoxServer.set_custom_string_property(payload['key']
|
80
|
+
Rox::Server::RoxServer.set_custom_string_property(payload['key']) do |_context|
|
81
|
+
raise_(StandardError.new "custom property generator error")
|
82
|
+
end
|
96
83
|
[200, JSON.generate({ result: 'done' })]
|
97
84
|
when 'setCustomStringProperty'
|
98
85
|
Rox::Server::RoxServer.set_custom_string_property(payload['key'], payload['value'])
|
99
86
|
[200, JSON.generate({ result: 'done' })]
|
100
87
|
when 'dynamicFlagValue'
|
101
|
-
|
102
|
-
puts "There is a payload[context] => #{payload['context']}"
|
103
|
-
payload['context'].each do |context|
|
104
|
-
key = context[0]
|
105
|
-
value = context[1]
|
106
|
-
Rox::Server::RoxServer.set_custom_string_property(key, value) if value.instance_of? String
|
107
|
-
if value.instance_of? TrueClass
|
108
|
-
Rox::Server::RoxServer.set_custom_boolean_property(key, value)
|
109
|
-
elsif value.instance_of? FalseClass
|
110
|
-
Rox::Server::RoxServer.set_custom_boolean_property(key, value)
|
111
|
-
end
|
112
|
-
Rox::Server::RoxServer.set_custom_int_property(key, value) if value.instance_of? Integer
|
113
|
-
Rox::Server::RoxServer.set_custom_float_property(key, value) if value.instance_of? Float
|
114
|
-
end
|
115
|
-
end
|
116
|
-
result = Rox::Server::RoxServer.dynamic_api.value(payload['flag'], payload['defaultValue'], payload['context'],
|
117
|
-
[])
|
88
|
+
result = Rox::Server::RoxServer.dynamic_api.value(payload['flag'], payload['defaultValue'], payload['context'], [])
|
118
89
|
[200, JSON.generate({ result: result })]
|
119
90
|
when 'dynamicFlagIsEnabled'
|
120
|
-
payload['context']&.each do |context|
|
121
|
-
key = context[0]
|
122
|
-
value = context[1]
|
123
|
-
Rox::Server::RoxServer.set_custom_string_property(key, value) if value.instance_of? String
|
124
|
-
if value.instance_of? TrueClass
|
125
|
-
Rox::Server::RoxServer.set_custom_boolean_property(key, value)
|
126
|
-
elsif value.instance_of? FalseClass
|
127
|
-
Rox::Server::RoxServer.set_custom_boolean_property(key, value)
|
128
|
-
end
|
129
|
-
Rox::Server::RoxServer.set_custom_int_property(key, value) if value.instance_of? Integer
|
130
|
-
Rox::Server::RoxServer.set_custom_float_property(key, value) if value.instance_of? Float
|
131
|
-
end
|
132
91
|
result = Rox::Server::RoxServer.dynamic_api.enabled?(payload['flag'], payload['defaultValue'], payload['context'])
|
133
92
|
[200, JSON.generate({ result: result })]
|
134
93
|
when 'setupAndAwait'
|
135
94
|
env = 'stam'
|
95
|
+
fetch_interval = nil
|
136
96
|
if payload['options']
|
137
97
|
options = payload['options']
|
98
|
+
env = options['env']
|
99
|
+
fetch_interval = options['fetchInterval']
|
138
100
|
if options['configuration']
|
139
101
|
configuration = options['configuration']
|
140
|
-
env = configuration['env']
|
141
102
|
end
|
142
103
|
end
|
104
|
+
network_config = nil
|
143
105
|
case env
|
106
|
+
when 'container'
|
107
|
+
network_config = Rox::Server::NetworkConfigurationsOptions.new(
|
108
|
+
get_config_api_endpoint: configuration['CD_API_ENDPOINT']&.chomp('/'),
|
109
|
+
get_config_cloud_endpoint: configuration['CD_S3_ENDPOINT']&.chomp('/'),
|
110
|
+
send_state_api_endpoint: configuration['SS_API_ENDPOINT']&.chomp('/'),
|
111
|
+
send_state_cloud_endpoint: configuration['SS_S3_ENDPOINT']&.chomp('/'),
|
112
|
+
analytics_endpoint: configuration['ANALYTICS_ENDPOINT']&.chomp('/'),
|
113
|
+
push_notification_endpoint: configuration['NOTIFICATIONS_ENDPOINT']&.chomp('/'))
|
144
114
|
when 'qa'
|
145
115
|
ENV['ROLLOUT_MODE'] = 'QA'
|
146
116
|
when 'localhost'
|
147
117
|
ENV['ROLLOUT_MODE'] = 'LOCAL'
|
148
118
|
end
|
149
|
-
options = Rox::Server::RoxOptions.new(logger: ServerLogger.new)
|
119
|
+
options = Rox::Server::RoxOptions.new(fetch_interval: fetch_interval,logger: ServerLogger.new, network_configurations_options: network_config)
|
150
120
|
puts "options => #{options}"
|
151
121
|
Rox::Server::RoxServer.setup(payload['key'], options).value
|
152
122
|
[200, JSON.generate({ result: 'done' })]
|
data/example/local.rb
CHANGED
@@ -4,8 +4,8 @@ require 'rox/server/rox_server'
|
|
4
4
|
require 'rox/server/rox_options'
|
5
5
|
|
6
6
|
API_HOST = 'http://localhost:8557'.freeze
|
7
|
-
APP_KEY = '
|
8
|
-
|
7
|
+
APP_KEY = '600571e330819d4842999e4f'.freeze
|
8
|
+
DEV_MODE_SECRET = 'e56cda16749d8d0a9b91d34c'.freeze
|
9
9
|
|
10
10
|
class Flags
|
11
11
|
attr_accessor :boolean_flag, :string_flag, :int_flag, :double_flag
|
@@ -23,11 +23,11 @@ flags = Flags.new
|
|
23
23
|
Rox::Server::RoxServer.register(flags)
|
24
24
|
|
25
25
|
options = Rox::Server::RoxOptions.new(
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
self_managed_options: Rox::Server::SelfManagedOptions.new(
|
27
|
+
server_url: API_HOST,
|
28
|
+
analytics_url: 'http://127.0.0.1:8787'
|
29
|
+
),
|
30
|
+
dev_mode_key: DEV_MODE_SECRET
|
31
31
|
)
|
32
32
|
|
33
33
|
Rox::Server::RoxServer.setup(APP_KEY, options)
|
@@ -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
|