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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcce6b53dd45a0b6b4f19fbd9de5e96bf2d10ff1747d105aa734407dff704b6e
4
- data.tar.gz: 65b3a4d75753ff880fb48bf02a398176ccd7aa04b889b90c84ea65e36d55272a
3
+ metadata.gz: 3b095c62f2f1876eac8bece177cbc292ed6686ea13d6ba6988b7e8d5cdf17ef7
4
+ data.tar.gz: 8e5a23ec4064547130a6e9ed4c349a5278b51fca95cbe48d9df033696252e6a8
5
5
  SHA512:
6
- metadata.gz: c4e727dd53858f6b413278088447181fb184bd5d5ed7d7b9b129b07a20073e73c44cb0e26a4792bacaedb3349af3859403264a5ee5fee96cb61b704341c93a79
7
- data.tar.gz: 1ee11c6663d4edf1ac1237c4a2aa5872b7f4da009467bdc93fd0c2cde9d7083f6cdb499d73b6290cf4edb212dd9e93154ce7f80d45bee9bfe75b6372c84a81f6
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
- symbolize_keys!(opts)
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, @data_plane_url, @write_key, opts)
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 @queue.length < @max_queue_size
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
- raise ArgumentError, 'Write key must be initialized' if @write_key.nil?
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
- 'User-Agent' => "rudderanalytics-ruby/#{Analytics::VERSION}" }
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 Segment Spec
5
+ # Handles parsing fields according to the RudderStack Spec
6
6
  #
7
- # @see https://segment.com/docs/spec/
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
- traits = fields[:traits] || {}
41
- check_is_hash!(traits, 'traits')
42
- isoify_dates! traits
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
- check_is_hash!(traits, 'traits')
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 = fields[: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.to_s,
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 = fields[: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
- :messageId => message_id,
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
- parsed[:userId] = fields[:user_id] if fields[:user_id]
155
- parsed[:anonymousId] = fields[:anonymous_id] if fields[:anonymous_id]
156
- parsed[:integrations] = fields[:integrations] if fields[:integrations]
157
-
158
- # Not in spec, retained for backward compatibility
159
- parsed[:options] = fields[:options] if fields[:options]
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
- raise ArgumentError, 'Must supply either user_id or anonymous_id' unless fields[:user_id] || fields[:anonymous_id]
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 obj.nil? || (obj.is_a?(String) && obj.empty?)
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 Request
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
- # public: Creates a new request object to send analytics batch
22
- #
23
- def initialize(options = {})
24
- options[:host] ||= HOST
25
- options[:port] ||= PORT
26
- options[:ssl] ||= SSL
27
- @headers = options[:headers] || HEADERS
28
- @path = options[:path] || PATH
29
- @retries = options[:retries] || RETRIES
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 = options[: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
- # public: Posts the write key and batch of messages to the API.
41
+ # Sends a batch of messages to the API
52
42
  #
53
- # returns - Response of the status and error if it exists
54
- def post(write_key, batch)
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
- payload = JSON.generate(
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
- puts payload
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.each_with_object({}) do |(k, v), memo|
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, fraction_digits = 3)
70
- fraction = (('.%06i' % time.usec)[0, fraction_digits + 1] if fraction_digits > 0)
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 < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
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,6 +2,6 @@
2
2
 
3
3
  module Rudder
4
4
  class Analytics
5
- VERSION = '0.0.5'
5
+ VERSION = '2.0.0'
6
6
  end
7
7
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rudder/analytics/defaults'
4
4
  require 'rudder/analytics/message_batch'
5
- require 'rudder/analytics/request'
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, data_plane_url, write_key, options = {})
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 = options[:ssl]
32
- @on_error = options[:on_error] || proc { |status, error| }
33
- batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE
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.
@@ -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/request'
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.5
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: 2020-04-10 00:00:00.000000000 Z
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: soumya@rudderlabs.com
140
+ email: arnab@rudderstack.com
141
141
  executables:
142
- - analytics-rudder.rb
142
+ - analytics_rudder.rb
143
143
  extensions: []
144
144
  extra_rdoc_files: []
145
145
  files:
146
- - bin/analytics-rudder.rb
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
@@ -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