analytics-ruby 2.2.6.pre → 2.3.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 +22 -7
- data/lib/segment/analytics.rb +3 -2
- data/lib/segment/analytics/client.rb +37 -272
- data/lib/segment/analytics/field_parser.rb +192 -0
- data/lib/segment/analytics/message_batch.rb +8 -1
- data/lib/segment/analytics/{request.rb → transport.rb} +12 -8
- data/lib/segment/analytics/utils.rb +2 -6
- data/lib/segment/analytics/version.rb +1 -1
- data/lib/segment/analytics/worker.rb +14 -4
- metadata +9 -59
- data/Gemfile +0 -2
- data/History.md +0 -234
- data/Makefile +0 -17
- data/README.md +0 -84
- data/RELEASING.md +0 -9
- data/Rakefile +0 -33
- data/analytics-ruby.gemspec +0 -37
- data/codecov.yml +0 -2
- data/spec/helpers/runscope_client.rb +0 -38
- data/spec/isolated/json_example.rb +0 -9
- data/spec/isolated/with_active_support.rb +0 -11
- data/spec/isolated/with_active_support_and_oj.rb +0 -16
- data/spec/isolated/with_oj.rb +0 -13
- data/spec/segment/analytics/backoff_policy_spec.rb +0 -92
- data/spec/segment/analytics/client_spec.rb +0 -328
- data/spec/segment/analytics/e2e_spec.rb +0 -54
- data/spec/segment/analytics/message_batch_spec.rb +0 -50
- data/spec/segment/analytics/request_spec.rb +0 -244
- data/spec/segment/analytics/response_spec.rb +0 -30
- data/spec/segment/analytics/worker_spec.rb +0 -110
- data/spec/segment/analytics_spec.rb +0 -120
- data/spec/spec_helper.rb +0 -128
@@ -0,0 +1,192 @@
|
|
1
|
+
module Segment
|
2
|
+
class Analytics
|
3
|
+
# Handles parsing fields according to the Segment Spec
|
4
|
+
#
|
5
|
+
# @see https://segment.com/docs/spec/
|
6
|
+
class FieldParser
|
7
|
+
class << self
|
8
|
+
include Segment::Analytics::Utils
|
9
|
+
|
10
|
+
# In addition to the common fields, track accepts:
|
11
|
+
#
|
12
|
+
# - "event"
|
13
|
+
# - "properties"
|
14
|
+
def parse_for_track(fields)
|
15
|
+
common = parse_common_fields(fields)
|
16
|
+
|
17
|
+
event = fields[:event]
|
18
|
+
properties = fields[:properties] || {}
|
19
|
+
|
20
|
+
check_presence!(event, 'event')
|
21
|
+
check_is_hash!(properties, 'properties')
|
22
|
+
|
23
|
+
isoify_dates! properties
|
24
|
+
|
25
|
+
common.merge({
|
26
|
+
:type => 'track',
|
27
|
+
:event => event.to_s,
|
28
|
+
:properties => properties
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
# In addition to the common fields, identify accepts:
|
33
|
+
#
|
34
|
+
# - "traits"
|
35
|
+
def parse_for_identify(fields)
|
36
|
+
common = parse_common_fields(fields)
|
37
|
+
|
38
|
+
traits = fields[:traits] || {}
|
39
|
+
check_is_hash!(traits, 'traits')
|
40
|
+
isoify_dates! traits
|
41
|
+
|
42
|
+
common.merge({
|
43
|
+
:type => 'identify',
|
44
|
+
:traits => traits
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
# In addition to the common fields, alias accepts:
|
49
|
+
#
|
50
|
+
# - "previous_id"
|
51
|
+
def parse_for_alias(fields)
|
52
|
+
common = parse_common_fields(fields)
|
53
|
+
|
54
|
+
previous_id = fields[:previous_id]
|
55
|
+
check_presence!(previous_id, 'previous_id')
|
56
|
+
|
57
|
+
common.merge({
|
58
|
+
:type => 'alias',
|
59
|
+
:previousId => previous_id
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
# In addition to the common fields, group accepts:
|
64
|
+
#
|
65
|
+
# - "group_id"
|
66
|
+
# - "traits"
|
67
|
+
def parse_for_group(fields)
|
68
|
+
common = parse_common_fields(fields)
|
69
|
+
|
70
|
+
group_id = fields[:group_id]
|
71
|
+
traits = fields[:traits] || {}
|
72
|
+
|
73
|
+
check_presence!(group_id, 'group_id')
|
74
|
+
check_is_hash!(traits, 'traits')
|
75
|
+
|
76
|
+
isoify_dates! traits
|
77
|
+
|
78
|
+
common.merge({
|
79
|
+
:type => 'group',
|
80
|
+
:groupId => group_id,
|
81
|
+
:traits => traits
|
82
|
+
})
|
83
|
+
end
|
84
|
+
|
85
|
+
# In addition to the common fields, page accepts:
|
86
|
+
#
|
87
|
+
# - "name"
|
88
|
+
# - "properties"
|
89
|
+
def parse_for_page(fields)
|
90
|
+
common = parse_common_fields(fields)
|
91
|
+
|
92
|
+
name = fields[:name] || ''
|
93
|
+
properties = fields[:properties] || {}
|
94
|
+
|
95
|
+
check_is_hash!(properties, 'properties')
|
96
|
+
|
97
|
+
isoify_dates! properties
|
98
|
+
|
99
|
+
common.merge({
|
100
|
+
:type => 'page',
|
101
|
+
:name => name.to_s,
|
102
|
+
:properties => properties
|
103
|
+
})
|
104
|
+
end
|
105
|
+
|
106
|
+
# In addition to the common fields, screen accepts:
|
107
|
+
#
|
108
|
+
# - "name"
|
109
|
+
# - "properties"
|
110
|
+
# - "category" (Not in spec, retained for backward compatibility"
|
111
|
+
def parse_for_screen(fields)
|
112
|
+
common = parse_common_fields(fields)
|
113
|
+
|
114
|
+
name = fields[:name]
|
115
|
+
properties = fields[:properties] || {}
|
116
|
+
category = fields[:category]
|
117
|
+
|
118
|
+
check_presence!(name, 'name')
|
119
|
+
check_is_hash!(properties, 'properties')
|
120
|
+
|
121
|
+
isoify_dates! properties
|
122
|
+
|
123
|
+
parsed = common.merge({
|
124
|
+
:type => 'screen',
|
125
|
+
:name => name,
|
126
|
+
:properties => properties
|
127
|
+
})
|
128
|
+
|
129
|
+
parsed[:category] = category if category
|
130
|
+
|
131
|
+
parsed
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def parse_common_fields(fields)
|
137
|
+
timestamp = fields[:timestamp] || Time.new
|
138
|
+
message_id = fields[:message_id].to_s if fields[:message_id]
|
139
|
+
context = fields[:context] || {}
|
140
|
+
|
141
|
+
check_user_id! fields
|
142
|
+
check_timestamp! timestamp
|
143
|
+
|
144
|
+
add_context! context
|
145
|
+
|
146
|
+
parsed = {
|
147
|
+
:context => context,
|
148
|
+
:messageId => message_id,
|
149
|
+
:timestamp => datetime_in_iso8601(timestamp)
|
150
|
+
}
|
151
|
+
|
152
|
+
parsed[:userId] = fields[:user_id] if fields[:user_id]
|
153
|
+
parsed[:anonymousId] = fields[:anonymous_id] if fields[:anonymous_id]
|
154
|
+
parsed[:integrations] = fields[:integrations] if fields[:integrations]
|
155
|
+
|
156
|
+
# Not in spec, retained for backward compatibility
|
157
|
+
parsed[:options] = fields[:options] if fields[:options]
|
158
|
+
|
159
|
+
parsed
|
160
|
+
end
|
161
|
+
|
162
|
+
def check_user_id!(fields)
|
163
|
+
unless fields[:user_id] || fields[:anonymous_id]
|
164
|
+
raise ArgumentError, 'Must supply either user_id or anonymous_id'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_timestamp!(timestamp)
|
169
|
+
raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_context!(context)
|
173
|
+
context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s }
|
174
|
+
end
|
175
|
+
|
176
|
+
# private: Ensures that a string is non-empty
|
177
|
+
#
|
178
|
+
# obj - String|Number that must be non-blank
|
179
|
+
# name - Name of the validated value
|
180
|
+
def check_presence!(obj, name)
|
181
|
+
if obj.nil? || (obj.is_a?(String) && obj.empty?)
|
182
|
+
raise ArgumentError, "#{name} must be given"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def check_is_hash!(obj, name)
|
187
|
+
raise ArgumentError, "#{name} must be a Hash" unless obj.is_a? Hash
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -5,6 +5,8 @@ module Segment
|
|
5
5
|
class Analytics
|
6
6
|
# A batch of `Message`s to be sent to the API
|
7
7
|
class MessageBatch
|
8
|
+
class JSONGenerationError < StandardError; end
|
9
|
+
|
8
10
|
extend Forwardable
|
9
11
|
include Segment::Analytics::Logging
|
10
12
|
include Segment::Analytics::Defaults::MessageBatch
|
@@ -16,8 +18,13 @@ module Segment
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def <<(message)
|
19
|
-
|
21
|
+
begin
|
22
|
+
message_json = message.to_json
|
23
|
+
rescue StandardError => e
|
24
|
+
raise JSONGenerationError, "Serialization error: #{e}"
|
25
|
+
end
|
20
26
|
|
27
|
+
message_json_size = message_json.bytesize
|
21
28
|
if message_too_big?(message_json_size)
|
22
29
|
logger.error('a message exceeded the maximum allowed size')
|
23
30
|
else
|
@@ -9,13 +9,11 @@ require 'json'
|
|
9
9
|
|
10
10
|
module Segment
|
11
11
|
class Analytics
|
12
|
-
class
|
12
|
+
class Transport
|
13
13
|
include Segment::Analytics::Defaults::Request
|
14
14
|
include Segment::Analytics::Utils
|
15
15
|
include Segment::Analytics::Logging
|
16
16
|
|
17
|
-
# public: Creates a new request object to send analytics batch
|
18
|
-
#
|
19
17
|
def initialize(options = {})
|
20
18
|
options[:host] ||= HOST
|
21
19
|
options[:port] ||= PORT
|
@@ -34,10 +32,10 @@ module Segment
|
|
34
32
|
@http = http
|
35
33
|
end
|
36
34
|
|
37
|
-
#
|
35
|
+
# Sends a batch of messages to the API
|
38
36
|
#
|
39
|
-
#
|
40
|
-
def
|
37
|
+
# @return [Response] API response
|
38
|
+
def send(write_key, batch)
|
41
39
|
logger.debug("Sending request for #{batch.length} items")
|
42
40
|
|
43
41
|
last_response, exception = retry_with_backoff(@retries) do
|
@@ -53,12 +51,17 @@ module Segment
|
|
53
51
|
if exception
|
54
52
|
logger.error(exception.message)
|
55
53
|
exception.backtrace.each { |line| logger.error(line) }
|
56
|
-
Response.new(-1,
|
54
|
+
Response.new(-1, exception.to_s)
|
57
55
|
else
|
58
56
|
last_response
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
60
|
+
# Closes a persistent connection if it exists
|
61
|
+
def shutdown
|
62
|
+
@http.finish if @http.started?
|
63
|
+
end
|
64
|
+
|
62
65
|
private
|
63
66
|
|
64
67
|
def should_retry_request?(status_code, body)
|
@@ -113,10 +116,11 @@ module Segment
|
|
113
116
|
|
114
117
|
if self.class.stub
|
115
118
|
logger.debug "stubbed request to #{@path}: " \
|
116
|
-
"write key = #{write_key}, batch = JSON.generate(
|
119
|
+
"write key = #{write_key}, batch = #{JSON.generate(batch)}"
|
117
120
|
|
118
121
|
[200, '{}']
|
119
122
|
else
|
123
|
+
@http.start unless @http.started? # Maintain a persistent connection
|
120
124
|
response = @http.request(request, payload)
|
121
125
|
[response.code.to_i, response.body]
|
122
126
|
end
|
@@ -64,12 +64,8 @@ module Segment
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def time_in_iso8601(time
|
68
|
-
|
69
|
-
('.%06i' % time.usec)[0, fraction_digits + 1]
|
70
|
-
end
|
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.%6N')}#{formatted_offset(time, true, 'Z')}"
|
73
69
|
end
|
74
70
|
|
75
71
|
def date_in_iso8601(date)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'segment/analytics/defaults'
|
2
2
|
require 'segment/analytics/message_batch'
|
3
|
-
require 'segment/analytics/
|
3
|
+
require 'segment/analytics/transport'
|
4
4
|
require 'segment/analytics/utils'
|
5
5
|
|
6
6
|
module Segment
|
@@ -29,6 +29,7 @@ module Segment
|
|
29
29
|
batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE
|
30
30
|
@batch = MessageBatch.new(batch_size)
|
31
31
|
@lock = Mutex.new
|
32
|
+
@transport = Transport.new
|
32
33
|
end
|
33
34
|
|
34
35
|
# public: Continuously runs the loop to check for new events
|
@@ -38,15 +39,16 @@ module Segment
|
|
38
39
|
return if @queue.empty?
|
39
40
|
|
40
41
|
@lock.synchronize do
|
41
|
-
|
42
|
+
consume_message_from_queue! until @batch.full? || @queue.empty?
|
42
43
|
end
|
43
44
|
|
44
|
-
res =
|
45
|
-
|
45
|
+
res = @transport.send @write_key, @batch
|
46
46
|
@on_error.call(res.status, res.error) unless res.status == 200
|
47
47
|
|
48
48
|
@lock.synchronize { @batch.clear }
|
49
49
|
end
|
50
|
+
ensure
|
51
|
+
@transport.shutdown
|
50
52
|
end
|
51
53
|
|
52
54
|
# public: Check whether we have outstanding requests.
|
@@ -54,6 +56,14 @@ module Segment
|
|
54
56
|
def is_requesting?
|
55
57
|
@lock.synchronize { !@batch.empty? }
|
56
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def consume_message_from_queue!
|
63
|
+
@batch << @queue.pop
|
64
|
+
rescue MessageBatch::JSONGenerationError => e
|
65
|
+
@on_error.call(-1, e.to_s)
|
66
|
+
end
|
57
67
|
end
|
58
68
|
end
|
59
69
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: analytics-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Segment.io
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -17,7 +17,7 @@ dependencies:
|
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.4'
|
20
|
-
type: :
|
20
|
+
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
@@ -80,34 +80,6 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 4.1.11
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: faraday
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0.13'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0.13'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: pmap
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.1'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.1'
|
111
83
|
- !ruby/object:Gem::Dependency
|
112
84
|
name: oj
|
113
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,42 +129,21 @@ executables:
|
|
157
129
|
extensions: []
|
158
130
|
extra_rdoc_files: []
|
159
131
|
files:
|
160
|
-
- Gemfile
|
161
|
-
- History.md
|
162
|
-
- Makefile
|
163
|
-
- README.md
|
164
|
-
- RELEASING.md
|
165
|
-
- Rakefile
|
166
|
-
- analytics-ruby.gemspec
|
167
132
|
- bin/analytics
|
168
|
-
- codecov.yml
|
169
133
|
- lib/analytics-ruby.rb
|
170
134
|
- lib/segment.rb
|
171
135
|
- lib/segment/analytics.rb
|
172
136
|
- lib/segment/analytics/backoff_policy.rb
|
173
137
|
- lib/segment/analytics/client.rb
|
174
138
|
- lib/segment/analytics/defaults.rb
|
139
|
+
- lib/segment/analytics/field_parser.rb
|
175
140
|
- lib/segment/analytics/logging.rb
|
176
141
|
- lib/segment/analytics/message_batch.rb
|
177
|
-
- lib/segment/analytics/request.rb
|
178
142
|
- lib/segment/analytics/response.rb
|
143
|
+
- lib/segment/analytics/transport.rb
|
179
144
|
- lib/segment/analytics/utils.rb
|
180
145
|
- lib/segment/analytics/version.rb
|
181
146
|
- lib/segment/analytics/worker.rb
|
182
|
-
- spec/helpers/runscope_client.rb
|
183
|
-
- spec/isolated/json_example.rb
|
184
|
-
- spec/isolated/with_active_support.rb
|
185
|
-
- spec/isolated/with_active_support_and_oj.rb
|
186
|
-
- spec/isolated/with_oj.rb
|
187
|
-
- spec/segment/analytics/backoff_policy_spec.rb
|
188
|
-
- spec/segment/analytics/client_spec.rb
|
189
|
-
- spec/segment/analytics/e2e_spec.rb
|
190
|
-
- spec/segment/analytics/message_batch_spec.rb
|
191
|
-
- spec/segment/analytics/request_spec.rb
|
192
|
-
- spec/segment/analytics/response_spec.rb
|
193
|
-
- spec/segment/analytics/worker_spec.rb
|
194
|
-
- spec/segment/analytics_spec.rb
|
195
|
-
- spec/spec_helper.rb
|
196
147
|
homepage: https://github.com/segmentio/analytics-ruby
|
197
148
|
licenses:
|
198
149
|
- MIT
|
@@ -205,15 +156,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
205
156
|
requirements:
|
206
157
|
- - ">="
|
207
158
|
- !ruby/object:Gem::Version
|
208
|
-
version: '0'
|
159
|
+
version: '2.0'
|
209
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
161
|
requirements:
|
211
|
-
- - "
|
162
|
+
- - ">="
|
212
163
|
- !ruby/object:Gem::Version
|
213
|
-
version:
|
164
|
+
version: '0'
|
214
165
|
requirements: []
|
215
|
-
|
216
|
-
rubygems_version: 2.7.7
|
166
|
+
rubygems_version: 3.0.8
|
217
167
|
signing_key:
|
218
168
|
specification_version: 4
|
219
169
|
summary: Segment.io analytics library
|