rudder-sdk-ruby 0.0.5 → 2.0.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/bin/analytics_rudder.rb +112 -0
- data/lib/rudder/analytics/client.rb +25 -21
- data/lib/rudder/analytics/configuration.rb +33 -0
- data/lib/rudder/analytics/defaults.rb +1 -3
- data/lib/rudder/analytics/field_parser.rb +73 -49
- data/lib/rudder/analytics/test_queue.rb +58 -0
- data/lib/rudder/analytics/{request.rb → transport.rb} +40 -43
- data/lib/rudder/analytics/utils.rb +9 -9
- data/lib/rudder/analytics/version.rb +1 -1
- data/lib/rudder/analytics/worker.rb +12 -10
- data/lib/rudder/analytics.rb +1 -2
- metadata +9 -7
- data/bin/analytics-rudder.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b095c62f2f1876eac8bece177cbc292ed6686ea13d6ba6988b7e8d5cdf17ef7
|
4
|
+
data.tar.gz: 8e5a23ec4064547130a6e9ed4c349a5278b51fca95cbe48d9df033696252e6a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4022a4715a1081a836001135496fc7770eaa734fd760607230eef3668d438ee8adad94b91e7e78fbf31051328d1ae3507a3b9221b6c6d7d8bd4a4165f5290ca9
|
7
|
+
data.tar.gz: fb34b8f93df5589eb0cabecd6b22b61e15092df4c661bd244d864978ffc4e816baa311d97d41ed9dbbbadcfe0950b7a8f81704d6fd4594cccbd970cff9afd60d
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# !/usr/bin/env ruby
|
4
|
+
|
5
|
+
require 'rudder/analytics'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'commander/import'
|
8
|
+
require 'time'
|
9
|
+
require 'json'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
program :name, 'simulator.rb'
|
13
|
+
program :version, '0.0.1'
|
14
|
+
program :description, 'scripting simulator'
|
15
|
+
|
16
|
+
def json_hash(str)
|
17
|
+
return JSON.parse(str) if str
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run in dev with ruby -ilib bin/analytics_rudder.rb --writeKey=<write_key> --dataPlaneUrl=<data_plane_url>
|
21
|
+
# --type=track --userId=123456 --event=Test --properties={\"key_1\" => \"value_1\"} --trace
|
22
|
+
|
23
|
+
default_command :send
|
24
|
+
|
25
|
+
command :send do |c|
|
26
|
+
c.description = 'send a Rudder message'
|
27
|
+
|
28
|
+
c.option '--writeKey=<writeKey>', String, 'the Rudder writeKey'
|
29
|
+
c.option '--dataPlaneUrl=<dataPlaneUrl>', String, 'the Rudder data plane URL'
|
30
|
+
c.option '--type=<type>', String, 'The Rudder message type'
|
31
|
+
|
32
|
+
c.option '--userId=<userId>', String, 'the user id to send the event as'
|
33
|
+
c.option '--anonymousId=<anonymousId>', String, 'the anonymous user id to send the event as'
|
34
|
+
c.option '--context=<context>', 'additional context for the event (JSON-encoded)'
|
35
|
+
c.option '--integrations=<integrations>', 'additional integrations for the event (JSON-encoded)'
|
36
|
+
|
37
|
+
c.option '--event=<event>', String, 'the event name to send with the event'
|
38
|
+
c.option '--properties=<properties>', 'the event properties to send (JSON-encoded)'
|
39
|
+
|
40
|
+
c.option '--name=<name>', 'name of the screen or page to send with the message'
|
41
|
+
|
42
|
+
c.option '--traits=<traits>', 'the identify/group traits to send (JSON-encoded)'
|
43
|
+
|
44
|
+
c.option '--groupId=<groupId>', String, 'the group id'
|
45
|
+
c.option '--previousId=<previousId>', String, 'the previous id'
|
46
|
+
|
47
|
+
c.action do |_, options|
|
48
|
+
Analytics = Rudder::Analytics.new({
|
49
|
+
:write_key => options.writeKey,
|
50
|
+
:data_plane_url => options.dataPlaneUrl,
|
51
|
+
:on_error => proc { |_status, msg| print msg }
|
52
|
+
})
|
53
|
+
|
54
|
+
case options.type
|
55
|
+
when 'track'
|
56
|
+
Analytics.track({
|
57
|
+
:user_id => options.userId,
|
58
|
+
:event => options.event,
|
59
|
+
:anonymous_id => options.anonymousId,
|
60
|
+
:properties => json_hash(options.properties),
|
61
|
+
:context => json_hash(options.context),
|
62
|
+
:integrations => json_hash(options.integrations)
|
63
|
+
})
|
64
|
+
when 'page'
|
65
|
+
Analytics.page({
|
66
|
+
:user_id => options.userId,
|
67
|
+
:anonymous_id => options.anonymousId,
|
68
|
+
:name => options.name,
|
69
|
+
:properties => json_hash(options.properties),
|
70
|
+
:context => json_hash(options.context),
|
71
|
+
:integrations => json_hash(options.integrations)
|
72
|
+
})
|
73
|
+
when 'screen'
|
74
|
+
Analytics.screen({
|
75
|
+
:user_id => options.userId,
|
76
|
+
:anonymous_id => options.anonymousId,
|
77
|
+
:name => options.name,
|
78
|
+
:properties => json_hash(options.properties),
|
79
|
+
:context => json_hash(options.context),
|
80
|
+
:integrations => json_hash(options.integrations)
|
81
|
+
})
|
82
|
+
when 'identify'
|
83
|
+
Analytics.identify({
|
84
|
+
:user_id => options.userId,
|
85
|
+
:anonymous_id => options.anonymousId,
|
86
|
+
:traits => json_hash(options.traits),
|
87
|
+
:context => json_hash(options.context),
|
88
|
+
:integrations => json_hash(options.integrations)
|
89
|
+
})
|
90
|
+
when 'group'
|
91
|
+
Analytics.group({
|
92
|
+
:user_id => options.userId,
|
93
|
+
:anonymous_id => options.anonymousId,
|
94
|
+
:group_id => options.groupId,
|
95
|
+
:traits => json_hash(options.traits),
|
96
|
+
:context => json_hash(options.context),
|
97
|
+
:integrations => json_hash(options.integrations)
|
98
|
+
})
|
99
|
+
when 'alias'
|
100
|
+
Analytics.alias({
|
101
|
+
:previous_id => options.previousId,
|
102
|
+
:user_id => options.userId,
|
103
|
+
:anonymous_id => options.anonymousId,
|
104
|
+
:context => json_hash(options.context),
|
105
|
+
:integrations => json_hash(options.integrations)
|
106
|
+
})
|
107
|
+
else
|
108
|
+
raise "Invalid Message Type #{options.type}"
|
109
|
+
end
|
110
|
+
Analytics.flush
|
111
|
+
end
|
112
|
+
end
|
@@ -8,6 +8,8 @@ require 'rudder/analytics/logging'
|
|
8
8
|
require 'rudder/analytics/utils'
|
9
9
|
require 'rudder/analytics/worker'
|
10
10
|
require 'rudder/analytics/defaults'
|
11
|
+
require 'rudder/analytics/configuration'
|
12
|
+
require 'rudder/analytics/test_queue'
|
11
13
|
require 'net/http'
|
12
14
|
|
13
15
|
module Rudder
|
@@ -23,23 +25,11 @@ module Rudder
|
|
23
25
|
# remain queued.
|
24
26
|
# @option opts [Proc] :on_error Handles error calls from the API.
|
25
27
|
def initialize(opts = {})
|
26
|
-
|
27
|
-
|
28
|
+
@config = Configuration.new(opts)
|
28
29
|
@queue = Queue.new
|
29
|
-
@write_key = opts[:write_key]
|
30
|
-
@data_plane_url = opts[:data_plane_url]
|
31
|
-
@max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
32
30
|
@worker_mutex = Mutex.new
|
33
|
-
@worker = Worker.new(@queue, @
|
31
|
+
@worker = Worker.new(@queue, @config)
|
34
32
|
@worker_thread = nil
|
35
|
-
|
36
|
-
uri = URI(opts[:data_plane_url])
|
37
|
-
|
38
|
-
@host = uri.host
|
39
|
-
@port = uri.port
|
40
|
-
|
41
|
-
check_write_key!
|
42
|
-
|
43
33
|
at_exit { @worker_thread && @worker_thread[:should_exit] = true }
|
44
34
|
end
|
45
35
|
|
@@ -155,6 +145,12 @@ module Rudder
|
|
155
145
|
@queue.length
|
156
146
|
end
|
157
147
|
|
148
|
+
def test_queue
|
149
|
+
raise 'Test queue only available when setting :test to true.' unless @config.test
|
150
|
+
|
151
|
+
@test_queue ||= TestQueue.new
|
152
|
+
end
|
153
|
+
|
158
154
|
private
|
159
155
|
|
160
156
|
# private: Enqueues the action.
|
@@ -165,25 +161,33 @@ module Rudder
|
|
165
161
|
# add our request id for tracing purposes
|
166
162
|
action[:messageId] ||= uid
|
167
163
|
|
168
|
-
if @
|
164
|
+
if @config.test
|
165
|
+
test_queue << action
|
166
|
+
return true
|
167
|
+
end
|
168
|
+
|
169
|
+
if @queue.length < @config.max_queue_size
|
169
170
|
@queue << action
|
170
171
|
ensure_worker_running
|
171
172
|
|
172
173
|
true
|
173
174
|
else
|
174
175
|
logger.warn(
|
175
|
-
'Queue is full, dropping events. The :max_queue_size '
|
176
|
-
'configuration parameter can be increased to prevent this from ' \
|
177
|
-
'happening.'
|
176
|
+
'Queue is full, dropping events. The :max_queue_size configuration parameter can be increased to prevent this from happening.'
|
178
177
|
)
|
179
178
|
false
|
180
179
|
end
|
181
180
|
end
|
182
181
|
|
183
182
|
# private: Checks that the write_key is properly initialized
|
184
|
-
def check_write_key!
|
185
|
-
|
186
|
-
end
|
183
|
+
# def check_write_key!
|
184
|
+
# raise ArgumentError, 'Write key must be initialized' if @write_key.nil?
|
185
|
+
# end
|
186
|
+
|
187
|
+
# private: Checks that the data_plane_url is properly initialized
|
188
|
+
# def check_data_plane_url!
|
189
|
+
# raise ArgumentError, 'Data plane url must be initialized' if @data_plane_url.nil?
|
190
|
+
# end
|
187
191
|
|
188
192
|
def ensure_worker_running
|
189
193
|
return if worker_running?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rudder/analytics/utils'
|
4
|
+
|
5
|
+
module Rudder
|
6
|
+
class Analytics
|
7
|
+
class Configuration
|
8
|
+
include Rudder::Analytics::Utils
|
9
|
+
|
10
|
+
attr_reader :write_key, :data_plane_url, :on_error, :stub, :gzip, :ssl, :batch_size, :test, :max_queue_size, :backoff_policy, :retries
|
11
|
+
|
12
|
+
def initialize(settings = {})
|
13
|
+
symbolized_settings = symbolize_keys(settings)
|
14
|
+
|
15
|
+
@test = symbolized_settings[:test]
|
16
|
+
@write_key = symbolized_settings[:write_key]
|
17
|
+
@data_plane_url = symbolized_settings[:data_plane_url]
|
18
|
+
@max_queue_size = symbolized_settings[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
19
|
+
@ssl = symbolized_settings[:ssl]
|
20
|
+
@on_error = symbolized_settings[:on_error] || proc { |status, error| }
|
21
|
+
@stub = symbolized_settings[:stub]
|
22
|
+
@batch_size = symbolized_settings[:batch_size] || Defaults::MessageBatch::MAX_SIZE
|
23
|
+
@gzip = symbolized_settings[:gzip]
|
24
|
+
@backoff_policy = symbolized_settings[:backoff_policy]
|
25
|
+
@retries = symbolized_settings[:retries]
|
26
|
+
raise ArgumentError, 'Missing required option :write_key' \
|
27
|
+
unless @write_key
|
28
|
+
raise ArgumentError, 'Data plane url must be initialized' \
|
29
|
+
unless @data_plane_url
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -7,11 +7,9 @@ module Rudder
|
|
7
7
|
HOST = 'localhost'
|
8
8
|
PORT = 8080
|
9
9
|
PATH = '/v1/batch'
|
10
|
-
DATA_PLANE_URL = 'http://localhost:8080/v1/batch'
|
11
|
-
SSL = false
|
12
10
|
HEADERS = { 'Accept' => 'application/json',
|
13
11
|
'Content-Type' => 'application/json',
|
14
|
-
'
|
12
|
+
'Content-Encoding' => 'gzip' }
|
15
13
|
RETRIES = 10
|
16
14
|
end
|
17
15
|
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module Rudder
|
4
4
|
class Analytics
|
5
|
-
# Handles parsing fields according to the
|
5
|
+
# Handles parsing fields according to the RudderStack Spec
|
6
6
|
#
|
7
|
-
# @see https://
|
8
|
-
class FieldParser
|
7
|
+
# @see https://www.rudderstack.com/docs/event-spec/standard-events/
|
8
|
+
class FieldParser # rubocop:disable Metrics/ClassLength
|
9
9
|
class << self
|
10
10
|
include Rudder::Analytics::Utils
|
11
11
|
|
@@ -17,17 +17,11 @@ module Rudder
|
|
17
17
|
common = parse_common_fields(fields)
|
18
18
|
|
19
19
|
event = fields[:event]
|
20
|
-
properties = fields[:properties] || {}
|
21
|
-
|
22
20
|
check_presence!(event, 'event')
|
23
|
-
check_is_hash!(properties, 'properties')
|
24
|
-
|
25
|
-
isoify_dates! properties
|
26
21
|
|
27
22
|
common.merge({
|
28
23
|
:type => 'track',
|
29
|
-
:event => event.to_s
|
30
|
-
:properties => properties
|
24
|
+
:event => event.to_s
|
31
25
|
})
|
32
26
|
end
|
33
27
|
|
@@ -37,13 +31,18 @@ module Rudder
|
|
37
31
|
def parse_for_identify(fields)
|
38
32
|
common = parse_common_fields(fields)
|
39
33
|
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
# add the traits if present
|
35
|
+
if fields[:traits]
|
36
|
+
traits = fields[:traits]
|
37
|
+
context = common[:context].merge({ :traits => traits })
|
38
|
+
|
39
|
+
common = common.merge({
|
40
|
+
:context => context
|
41
|
+
})
|
42
|
+
end
|
43
43
|
|
44
44
|
common.merge({
|
45
|
-
:type => 'identify'
|
46
|
-
:traits => traits
|
45
|
+
:type => 'identify'
|
47
46
|
})
|
48
47
|
end
|
49
48
|
|
@@ -55,6 +54,7 @@ module Rudder
|
|
55
54
|
|
56
55
|
previous_id = fields[:previous_id]
|
57
56
|
check_presence!(previous_id, 'previous_id')
|
57
|
+
check_string(previous_id, 'previous_id')
|
58
58
|
|
59
59
|
common.merge({
|
60
60
|
:type => 'alias',
|
@@ -70,17 +70,12 @@ module Rudder
|
|
70
70
|
common = parse_common_fields(fields)
|
71
71
|
|
72
72
|
group_id = fields[:group_id]
|
73
|
-
traits = fields[:traits] || {}
|
74
|
-
|
75
73
|
check_presence!(group_id, 'group_id')
|
76
|
-
|
77
|
-
|
78
|
-
isoify_dates! traits
|
74
|
+
check_string(group_id, 'group_id')
|
79
75
|
|
80
76
|
common.merge({
|
81
77
|
:type => 'group',
|
82
|
-
:groupId => group_id
|
83
|
-
:traits => traits
|
78
|
+
:groupId => group_id
|
84
79
|
})
|
85
80
|
end
|
86
81
|
|
@@ -92,15 +87,13 @@ module Rudder
|
|
92
87
|
common = parse_common_fields(fields)
|
93
88
|
|
94
89
|
name = fields[:name] || ''
|
95
|
-
properties =
|
96
|
-
|
97
|
-
check_is_hash!(properties, 'properties')
|
98
|
-
|
99
|
-
isoify_dates! properties
|
90
|
+
properties = common[:properties] || {}
|
91
|
+
properties = properties.merge({ :name => name })
|
100
92
|
|
101
93
|
common.merge({
|
102
94
|
:type => 'page',
|
103
|
-
:name => name
|
95
|
+
:name => name,
|
96
|
+
:event => name,
|
104
97
|
:properties => properties
|
105
98
|
})
|
106
99
|
end
|
@@ -109,22 +102,21 @@ module Rudder
|
|
109
102
|
#
|
110
103
|
# - "name"
|
111
104
|
# - "properties"
|
112
|
-
# - "category" (Not in spec, retained for backward compatibility"
|
113
105
|
def parse_for_screen(fields)
|
114
106
|
common = parse_common_fields(fields)
|
115
107
|
|
116
108
|
name = fields[:name]
|
117
|
-
properties =
|
109
|
+
properties = common[:properties] || {}
|
110
|
+
properties = properties.merge({ :name => name })
|
111
|
+
|
118
112
|
category = fields[:category]
|
119
113
|
|
120
114
|
check_presence!(name, 'name')
|
121
|
-
check_is_hash!(properties, 'properties')
|
122
|
-
|
123
|
-
isoify_dates! properties
|
124
115
|
|
125
116
|
parsed = common.merge({
|
126
117
|
:type => 'screen',
|
127
118
|
:name => name,
|
119
|
+
:event => name,
|
128
120
|
:properties => properties
|
129
121
|
})
|
130
122
|
|
@@ -135,40 +127,68 @@ module Rudder
|
|
135
127
|
|
136
128
|
private
|
137
129
|
|
138
|
-
def parse_common_fields(fields)
|
139
|
-
timestamp = fields[:timestamp] || Time.new
|
140
|
-
message_id = fields[:message_id].to_s if fields[:message_id]
|
141
|
-
context = fields[:context] || {}
|
142
|
-
|
130
|
+
def parse_common_fields(fields) # rubocop:disable Metrics/AbcSize Metrics/CyclomaticComplexity Metrics/PerceivedComplexity
|
143
131
|
check_user_id! fields
|
132
|
+
|
133
|
+
current_time = Time.now.utc
|
134
|
+
timestamp = fields[:timestamp] || current_time
|
144
135
|
check_timestamp! timestamp
|
145
136
|
|
137
|
+
context = fields[:context] || {}
|
138
|
+
delete_library_from_context! context
|
146
139
|
add_context! context
|
147
140
|
|
148
141
|
parsed = {
|
149
142
|
:context => context,
|
150
|
-
:
|
151
|
-
:timestamp => datetime_in_iso8601(timestamp)
|
143
|
+
:integrations => fields[:integrations] || { :All => true },
|
144
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
145
|
+
:sentAt => datetime_in_iso8601(current_time),
|
146
|
+
:messageId => fields[:message_id] || uid,
|
147
|
+
:channel => 'server'
|
152
148
|
}
|
153
149
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
150
|
+
# add the userId if present
|
151
|
+
if fields[:user_id]
|
152
|
+
check_string(fields[:user_id], 'user_id')
|
153
|
+
parsed = parsed.merge({ :userId => fields[:user_id] })
|
154
|
+
end
|
155
|
+
# add the anonymousId if present
|
156
|
+
if fields[:anonymous_id]
|
157
|
+
check_string(fields[:anonymous_id], 'anonymous_id')
|
158
|
+
parsed = parsed.merge({ :anonymousId => fields[:anonymous_id] })
|
159
|
+
end
|
160
|
+
# add the properties if present
|
161
|
+
if fields[:properties]
|
162
|
+
properties = fields[:properties]
|
163
|
+
check_is_hash!(properties, 'properties')
|
164
|
+
isoify_dates! properties
|
165
|
+
parsed = parsed.merge({ :properties => properties })
|
166
|
+
end
|
167
|
+
# add the traits if present
|
168
|
+
if fields[:traits]
|
169
|
+
traits = fields[:traits]
|
170
|
+
check_is_hash!(traits, 'traits')
|
171
|
+
isoify_dates! traits
|
172
|
+
parsed = parsed.merge({ :traits => traits })
|
173
|
+
end
|
161
174
|
parsed
|
162
175
|
end
|
163
176
|
|
164
177
|
def check_user_id!(fields)
|
165
|
-
|
178
|
+
return unless blank?(fields[:user_id])
|
179
|
+
return unless blank?(fields[:anonymous_id])
|
180
|
+
|
181
|
+
raise ArgumentError, 'Must supply either user_id or anonymous_id'
|
166
182
|
end
|
167
183
|
|
168
184
|
def check_timestamp!(timestamp)
|
169
185
|
raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
|
170
186
|
end
|
171
187
|
|
188
|
+
def delete_library_from_context!(context)
|
189
|
+
context.delete(:library) if context # rubocop:disable Style/SafeNavigation
|
190
|
+
end
|
191
|
+
|
172
192
|
def add_context!(context)
|
173
193
|
context[:library] = { :name => 'rudderanalytics-ruby', :version => Rudder::Analytics::VERSION.to_s }
|
174
194
|
end
|
@@ -178,7 +198,11 @@ module Rudder
|
|
178
198
|
# obj - String|Number that must be non-blank
|
179
199
|
# name - Name of the validated value
|
180
200
|
def check_presence!(obj, name)
|
181
|
-
raise ArgumentError, "#{name} must be given" if
|
201
|
+
raise ArgumentError, "#{name} must be given" if blank?(obj)
|
202
|
+
end
|
203
|
+
|
204
|
+
def blank?(obj)
|
205
|
+
obj.nil? || (obj.is_a?(String) && obj.empty?)
|
182
206
|
end
|
183
207
|
|
184
208
|
def check_is_hash!(obj, name)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rudder
|
4
|
+
class Analytics
|
5
|
+
class TestQueue
|
6
|
+
attr_reader :messages
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset!
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
all[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def count
|
17
|
+
all.count
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(message)
|
21
|
+
all << message
|
22
|
+
send(message[:type]) << message
|
23
|
+
end
|
24
|
+
|
25
|
+
def alias
|
26
|
+
messages[:alias] ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def all
|
30
|
+
messages[:all] ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def group
|
34
|
+
messages[:group] ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def identify
|
38
|
+
messages[:identify] ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def page
|
42
|
+
messages[:page] ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def screen
|
46
|
+
messages[:screen] ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
def track
|
50
|
+
messages[:track] ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def reset!
|
54
|
+
@messages = {}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -10,48 +10,38 @@ require 'net/https'
|
|
10
10
|
require 'json'
|
11
11
|
require 'pry'
|
12
12
|
require 'uri'
|
13
|
+
require 'zlib'
|
13
14
|
|
14
15
|
module Rudder
|
15
16
|
class Analytics
|
16
|
-
class
|
17
|
+
class Transport
|
17
18
|
include Rudder::Analytics::Defaults::Request
|
18
19
|
include Rudder::Analytics::Utils
|
19
20
|
include Rudder::Analytics::Logging
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
def initialize(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
@backoff_policy =
|
31
|
-
options[:backoff_policy] || Rudder::Analytics::BackoffPolicy.new
|
32
|
-
|
33
|
-
uri = URI(options[:data_plane_url] || DATA_PLANE_URL)
|
34
|
-
# printf("************\n")
|
35
|
-
# printf("************\n")
|
36
|
-
# printf(options[:data_plane_url] || DATA_PLANE_URL)
|
37
|
-
# printf("\n************\n")
|
38
|
-
# printf(uri.host)
|
39
|
-
# printf("\n************\n")
|
40
|
-
# printf(uri.port.to_s)
|
41
|
-
# printf("************\n")
|
22
|
+
attr_reader :stub
|
23
|
+
|
24
|
+
def initialize(config)
|
25
|
+
@stub = config.stub || false
|
26
|
+
@path = PATH
|
27
|
+
@retries = config.retries || RETRIES
|
28
|
+
@backoff_policy = config.backoff_policy || Rudder::Analytics::BackoffPolicy.new
|
29
|
+
|
30
|
+
uri = URI(config.data_plane_url)
|
42
31
|
|
43
32
|
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
-
http.use_ssl =
|
33
|
+
http.use_ssl = config.ssl || true
|
45
34
|
http.read_timeout = 8
|
46
35
|
http.open_timeout = 4
|
47
36
|
|
48
37
|
@http = http
|
38
|
+
@gzip = config.gzip.nil? ? true : config.gzip
|
49
39
|
end
|
50
40
|
|
51
|
-
#
|
41
|
+
# Sends a batch of messages to the API
|
52
42
|
#
|
53
|
-
#
|
54
|
-
def
|
43
|
+
# @return [Response] API response
|
44
|
+
def send(write_key, batch)
|
55
45
|
logger.debug("Sending request for #{batch.length} items")
|
56
46
|
|
57
47
|
last_response, exception = retry_with_backoff(@retries) do
|
@@ -81,6 +71,11 @@ module Rudder
|
|
81
71
|
end
|
82
72
|
end
|
83
73
|
|
74
|
+
# Closes a persistent connection if it exists
|
75
|
+
def shutdown
|
76
|
+
@http.finish if @http.started?
|
77
|
+
end
|
78
|
+
|
84
79
|
private
|
85
80
|
|
86
81
|
def should_retry_request?(status_code, body)
|
@@ -126,32 +121,34 @@ module Rudder
|
|
126
121
|
|
127
122
|
# Sends a request for the batch, returns [status_code, body]
|
128
123
|
def send_request(write_key, batch)
|
129
|
-
|
130
|
-
:sentAt => datetime_in_iso8601(Time.now),
|
131
|
-
:batch => batch
|
132
|
-
)
|
133
|
-
request = Net::HTTP::Post.new(@path, @headers)
|
134
|
-
request.basic_auth(write_key, nil)
|
135
|
-
|
136
|
-
if self.class.stub
|
124
|
+
if stub
|
137
125
|
logger.debug "stubbed request to #{@path}: " \
|
138
126
|
"write key = #{write_key}, batch = #{JSON.generate(batch)}"
|
139
127
|
|
140
128
|
[200, '{}']
|
141
129
|
else
|
142
|
-
|
130
|
+
payload = {
|
131
|
+
:batch => batch
|
132
|
+
}
|
133
|
+
|
134
|
+
headers = HEADERS
|
135
|
+
|
136
|
+
if @gzip
|
137
|
+
gzip = Zlib::GzipWriter.new(StringIO.new)
|
138
|
+
gzip << payload.to_json
|
139
|
+
payload = gzip.close.string
|
140
|
+
else
|
141
|
+
headers.delete('Content-Encoding')
|
142
|
+
payload = JSON.generate(payload)
|
143
|
+
end
|
144
|
+
|
145
|
+
request = Net::HTTP::Post.new(@path, headers)
|
146
|
+
request.basic_auth(write_key, nil)
|
147
|
+
@http.start unless @http.started? # Maintain a persistent connection
|
143
148
|
response = @http.request(request, payload)
|
144
149
|
[response.code.to_i, response.body]
|
145
150
|
end
|
146
151
|
end
|
147
|
-
|
148
|
-
class << self
|
149
|
-
attr_writer :stub
|
150
|
-
|
151
|
-
def stub
|
152
|
-
@stub || ENV['STUB']
|
153
|
-
end
|
154
|
-
end
|
155
152
|
end
|
156
153
|
end
|
157
154
|
end
|
@@ -10,9 +10,7 @@ module Rudder
|
|
10
10
|
# public: Return a new hash with keys converted from strings to symbols
|
11
11
|
#
|
12
12
|
def symbolize_keys(hash)
|
13
|
-
hash.
|
14
|
-
memo[k.to_sym] = v
|
15
|
-
end
|
13
|
+
hash.transform_keys(&:to_sym)
|
16
14
|
end
|
17
15
|
|
18
16
|
# public: Convert hash keys from strings to symbols in place
|
@@ -50,7 +48,7 @@ module Rudder
|
|
50
48
|
arr = SecureRandom.random_bytes(16).unpack('NnnnnN')
|
51
49
|
arr[2] = (arr[2] & 0x0fff) | 0x4000
|
52
50
|
arr[3] = (arr[3] & 0x3fff) | 0x8000
|
53
|
-
'%08x-%04x-%04x-%04x-%04x%08x' % arr
|
51
|
+
'%08x-%04x-%04x-%04x-%04x%08x' % arr # rubocop:disable Style/FormatStringToken
|
54
52
|
end
|
55
53
|
|
56
54
|
def datetime_in_iso8601(datetime)
|
@@ -66,10 +64,8 @@ module Rudder
|
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
def time_in_iso8601(time
|
70
|
-
|
71
|
-
|
72
|
-
"#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}"
|
67
|
+
def time_in_iso8601(time)
|
68
|
+
"#{time.strftime('%Y-%m-%dT%H:%M:%S.%3N')}#{formatted_offset(time, true, 'Z')}"
|
73
69
|
end
|
74
70
|
|
75
71
|
def date_in_iso8601(date)
|
@@ -81,7 +77,11 @@ module Rudder
|
|
81
77
|
end
|
82
78
|
|
83
79
|
def seconds_to_utc_offset(seconds, colon = true)
|
84
|
-
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds
|
80
|
+
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds.negative? ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_string(obj, name)
|
84
|
+
raise ArgumentError, "#{name} must be a String" unless obj.is_a? String
|
85
85
|
end
|
86
86
|
|
87
87
|
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rudder/analytics/defaults'
|
4
4
|
require 'rudder/analytics/message_batch'
|
5
|
-
require 'rudder/analytics/
|
5
|
+
require 'rudder/analytics/transport'
|
6
6
|
require 'rudder/analytics/utils'
|
7
7
|
|
8
8
|
module Rudder
|
@@ -23,16 +23,15 @@ module Rudder
|
|
23
23
|
# batch_size - Fixnum of how many items to send in a batch
|
24
24
|
# on_error - Proc of what to do on an error
|
25
25
|
#
|
26
|
-
def initialize(queue,
|
27
|
-
symbolize_keys! options
|
26
|
+
def initialize(queue, config)
|
28
27
|
@queue = queue
|
29
|
-
@data_plane_url = data_plane_url
|
30
|
-
@write_key = write_key
|
31
|
-
@ssl =
|
32
|
-
@on_error =
|
33
|
-
|
34
|
-
@batch = MessageBatch.new(batch_size)
|
28
|
+
@data_plane_url = config.data_plane_url
|
29
|
+
@write_key = config.write_key
|
30
|
+
@ssl = config.ssl
|
31
|
+
@on_error = config.on_error
|
32
|
+
@batch = MessageBatch.new(config.batch_size)
|
35
33
|
@lock = Mutex.new
|
34
|
+
@transport = Transport.new(config)
|
36
35
|
end
|
37
36
|
|
38
37
|
# public: Continuously runs the loop to check for new events
|
@@ -45,11 +44,14 @@ module Rudder
|
|
45
44
|
consume_message_from_queue! until @batch.full? || @queue.empty?
|
46
45
|
end
|
47
46
|
|
48
|
-
res = Request.new(:data_plane_url => @data_plane_url, :ssl => @ssl).post @write_key, @batch
|
47
|
+
# res = Request.new(:data_plane_url => @data_plane_url, :ssl => @ssl).post @write_key, @batch
|
48
|
+
res = @transport.send @write_key, @batch
|
49
49
|
@on_error.call(res.status, res.error) unless res.status == 200
|
50
50
|
|
51
51
|
@lock.synchronize { @batch.clear }
|
52
52
|
end
|
53
|
+
ensure
|
54
|
+
@transport.shutdown
|
53
55
|
end
|
54
56
|
|
55
57
|
# public: Check whether we have outstanding requests.
|
data/lib/rudder/analytics.rb
CHANGED
@@ -6,7 +6,7 @@ require 'rudder/analytics/utils'
|
|
6
6
|
require 'rudder/analytics/field_parser'
|
7
7
|
require 'rudder/analytics/client'
|
8
8
|
require 'rudder/analytics/worker'
|
9
|
-
require 'rudder/analytics/
|
9
|
+
require 'rudder/analytics/transport'
|
10
10
|
require 'rudder/analytics/response'
|
11
11
|
require 'rudder/analytics/logging'
|
12
12
|
|
@@ -20,7 +20,6 @@ module Rudder
|
|
20
20
|
# @option options [Boolean] :stub (false) If true, requests don't hit the
|
21
21
|
# server and are stubbed to be successful.
|
22
22
|
def initialize(options = {})
|
23
|
-
Request.stub = options[:stub] if options.has_key?(:stub)
|
24
23
|
@client = Rudder::Analytics::Client.new options
|
25
24
|
end
|
26
25
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rudder-sdk-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rudder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -137,24 +137,26 @@ dependencies:
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: 0.1.4
|
139
139
|
description: The Rudder ruby analytics library
|
140
|
-
email:
|
140
|
+
email: arnab@rudderstack.com
|
141
141
|
executables:
|
142
|
-
-
|
142
|
+
- analytics_rudder.rb
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
|
-
- bin/
|
146
|
+
- bin/analytics_rudder.rb
|
147
147
|
- lib/rudder-sdk-ruby.rb
|
148
148
|
- lib/rudder.rb
|
149
149
|
- lib/rudder/analytics.rb
|
150
150
|
- lib/rudder/analytics/backoff_policy.rb
|
151
151
|
- lib/rudder/analytics/client.rb
|
152
|
+
- lib/rudder/analytics/configuration.rb
|
152
153
|
- lib/rudder/analytics/defaults.rb
|
153
154
|
- lib/rudder/analytics/field_parser.rb
|
154
155
|
- lib/rudder/analytics/logging.rb
|
155
156
|
- lib/rudder/analytics/message_batch.rb
|
156
|
-
- lib/rudder/analytics/request.rb
|
157
157
|
- lib/rudder/analytics/response.rb
|
158
|
+
- lib/rudder/analytics/test_queue.rb
|
159
|
+
- lib/rudder/analytics/transport.rb
|
158
160
|
- lib/rudder/analytics/utils.rb
|
159
161
|
- lib/rudder/analytics/version.rb
|
160
162
|
- lib/rudder/analytics/worker.rb
|
@@ -177,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
179
|
- !ruby/object:Gem::Version
|
178
180
|
version: '0'
|
179
181
|
requirements: []
|
180
|
-
rubygems_version: 3.0.3
|
182
|
+
rubygems_version: 3.0.3.1
|
181
183
|
signing_key:
|
182
184
|
specification_version: 4
|
183
185
|
summary: Rudder analytics library
|
data/bin/analytics-rudder.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'rudder/analytics'
|
4
|
-
require 'rubygems'
|
5
|
-
require 'commander/import'
|
6
|
-
require 'time'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
program :name, 'simulator.rb'
|
10
|
-
program :version, '0.0.1'
|
11
|
-
program :description, 'scripting simulator'
|
12
|
-
|
13
|
-
def json_hash(str)
|
14
|
-
if str
|
15
|
-
return JSON.parse(str)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# analytics -method=<method> -segment-write-key=<segmentWriteKey> [options]
|
20
|
-
# Run in dev with ruby -ilib bin/analytics -method=<method> -segment-write-key=<segmentWriteKey> [options]
|
21
|
-
|
22
|
-
default_command :send
|
23
|
-
|
24
|
-
command :send do |c|
|
25
|
-
c.description = 'send a segment message'
|
26
|
-
|
27
|
-
c.option '--writeKey=<writeKey>', String, 'the Rudder writeKey'
|
28
|
-
c.option '--dataPlaneUrl=<dataPlaneUrl>', String, 'the Rudder data plane URL'
|
29
|
-
c.option '--type=<type>', String, 'The Segment message type'
|
30
|
-
|
31
|
-
c.option '--userId=<userId>', String, 'the user id to send the event as'
|
32
|
-
c.option '--anonymousId=<anonymousId>', String, 'the anonymous user id to send the event as'
|
33
|
-
c.option '--context=<context>', 'additional context for the event (JSON-encoded)'
|
34
|
-
c.option '--integrations=<integrations>', 'additional integrations for the event (JSON-encoded)'
|
35
|
-
|
36
|
-
c.option '--event=<event>', String, 'the event name to send with the event'
|
37
|
-
c.option '--properties=<properties>', 'the event properties to send (JSON-encoded)'
|
38
|
-
|
39
|
-
c.option '--name=<name>', 'name of the screen or page to send with the message'
|
40
|
-
|
41
|
-
c.option '--traits=<traits>', 'the identify/group traits to send (JSON-encoded)'
|
42
|
-
|
43
|
-
c.option '--groupId=<groupId>', String, 'the group id'
|
44
|
-
c.option '--previousId=<previousId>', String, 'the previous id'
|
45
|
-
|
46
|
-
c.action do |args, options|
|
47
|
-
Analytics = Rudder::Analytics.new({
|
48
|
-
write_key: options.writeKey,
|
49
|
-
data_plane_url: options.dataPlaneUrl,
|
50
|
-
on_error: Proc.new { |status, msg| print msg }
|
51
|
-
})
|
52
|
-
|
53
|
-
case options.type
|
54
|
-
when "track"
|
55
|
-
Analytics.track({
|
56
|
-
user_id: options.userId,
|
57
|
-
event: options.event,
|
58
|
-
anonymous_id: options.anonymousId,
|
59
|
-
properties: json_hash(options.properties),
|
60
|
-
context: json_hash(options.context),
|
61
|
-
integrations: json_hash(options.integrations)
|
62
|
-
})
|
63
|
-
when "page"
|
64
|
-
Analytics.page({
|
65
|
-
user_id: options.userId,
|
66
|
-
anonymous_id: options.anonymousId,
|
67
|
-
name: options.name,
|
68
|
-
properties: json_hash(options.properties),
|
69
|
-
context: json_hash(options.context),
|
70
|
-
integrations: json_hash(options.integrations)
|
71
|
-
})
|
72
|
-
when "screen"
|
73
|
-
Analytics.screen({
|
74
|
-
user_id: options.userId,
|
75
|
-
anonymous_id: options.anonymousId,
|
76
|
-
name: options.name,
|
77
|
-
properties: json_hash(options.properties),
|
78
|
-
context: json_hash(options.context),
|
79
|
-
integrations: json_hash(options.integrations)
|
80
|
-
})
|
81
|
-
when "identify"
|
82
|
-
Analytics.identify({
|
83
|
-
user_id: options.userId,
|
84
|
-
anonymous_id: options.anonymousId,
|
85
|
-
traits: json_hash(options.traits),
|
86
|
-
context: json_hash(options.context),
|
87
|
-
integrations: json_hash(options.integrations)
|
88
|
-
})
|
89
|
-
when "group"
|
90
|
-
Analytics.group({
|
91
|
-
user_id: options.userId,
|
92
|
-
anonymous_id: options.anonymousId,
|
93
|
-
group_id: options.groupId,
|
94
|
-
traits: json_hash(options.traits),
|
95
|
-
context: json_hash(options.context),
|
96
|
-
integrations: json_hash(options.integrations)
|
97
|
-
})
|
98
|
-
when "alias"
|
99
|
-
Analytics.alias({
|
100
|
-
previous_id: options.previousId,
|
101
|
-
user_id: options.userId,
|
102
|
-
anonymous_id: options.anonymousId,
|
103
|
-
context: json_hash(options.context),
|
104
|
-
integrations: json_hash(options.integrations)
|
105
|
-
})
|
106
|
-
else
|
107
|
-
raise "Invalid Message Type #{options.type}"
|
108
|
-
end
|
109
|
-
Analytics.flush
|
110
|
-
end
|
111
|
-
end
|