snowplow-tracker 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +1 -1
- data/lib/snowplow-tracker.rb +1 -0
- data/lib/snowplow-tracker/contracts.rb +0 -1
- data/lib/snowplow-tracker/emitters.rb +114 -64
- data/lib/snowplow-tracker/payload.rb +2 -1
- data/lib/snowplow-tracker/self_describing_json.rb +34 -0
- data/lib/snowplow-tracker/subject.rb +10 -1
- data/lib/snowplow-tracker/tracker.rb +17 -26
- data/lib/snowplow-tracker/version.rb +1 -1
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NDkzMWJkNzQyZjVmNjk0NmQ0YjI5ODA5YzkxM2Y1NmZhZmEwNTgzNA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NTU5NDI2M2UzYTQ0M2ZhNGEyYjM2ZDdjMzQwZjA4ZGYyNDQ3OWU0OA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjJiM2Y1ZWM2Y2Y3MTNkZDNmNWM0N2IwMGNkOTAzMTlhNjAxZjk5MTk3ZmUz
|
10
|
+
MjcyNTYwNmZjZjhmMWVlZGNiMmJiNGZiOGZlMmJlY2YxNDE1MjY0MGFlYzI0
|
11
|
+
M2E2OGY5YTkwM2M4Y2M1NjQyNGEyNzIxNzYzNDk1ZDY1MGU0NDg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjgxODJjMWY0NmIwOTNjODNmZmQ1NTZkOTMwNDBkODMxODZhMzc1Njk2ODc1
|
14
|
+
NDkzZDYzNWMyZDQ0MzBjYzRiNzI3ZTdkNjI4OWY5ZjUyMTgzMjI3ZWYyOTI4
|
15
|
+
NGZhYTljNTFkOTRhYWQ5MzEyMGFmMmU0ZGNhNGFjZWQ0YzQ2NmI=
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ruby Analytics for Snowplow
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/snowplow-tracker.svg)](http://badge.fury.io/rb/snowplow-tracker)
|
3
|
-
[![Build Status](https://travis-ci.org/snowplow/snowplow-ruby-tracker.png)](https://travis-ci.org/snowplow/snowplow-ruby-tracker)
|
3
|
+
[![Build Status](https://travis-ci.org/snowplow/snowplow-ruby-tracker.png?branch=master)](https://travis-ci.org/snowplow/snowplow-ruby-tracker)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/snowplow/snowplow-ruby-tracker.png)](https://codeclimate.com/github/snowplow/snowplow-ruby-tracker)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/snowplow/snowplow-ruby-tracker/badge.png)](https://coveralls.io/r/snowplow/snowplow-ruby-tracker)
|
6
6
|
[![License][license-image]][license]
|
data/lib/snowplow-tracker.rb
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
|
16
16
|
require 'snowplow-tracker/contracts.rb'
|
17
17
|
require 'snowplow-tracker/version.rb'
|
18
|
+
require 'snowplow-tracker/self_describing_json.rb'
|
18
19
|
require 'snowplow-tracker/payload.rb'
|
19
20
|
require 'snowplow-tracker/subject.rb'
|
20
21
|
require 'snowplow-tracker/emitters.rb'
|
@@ -17,7 +17,6 @@ require 'net/https'
|
|
17
17
|
require 'set'
|
18
18
|
require 'logger'
|
19
19
|
require 'contracts'
|
20
|
-
include Contracts
|
21
20
|
|
22
21
|
module SnowplowTracker
|
23
22
|
|
@@ -26,13 +25,16 @@ module SnowplowTracker
|
|
26
25
|
|
27
26
|
class Emitter
|
28
27
|
|
28
|
+
include Contracts
|
29
|
+
|
29
30
|
@@ConfigHash = ({
|
30
31
|
:protocol => Maybe[Or['http', 'https']],
|
31
32
|
:port => Maybe[Num],
|
32
33
|
:method => Maybe[Or['get', 'post']],
|
33
34
|
:buffer_size => Maybe[Num],
|
34
35
|
:on_success => Maybe[Func[Num => Any]],
|
35
|
-
:on_failure => Maybe[Func[Num, Hash => Any]]
|
36
|
+
:on_failure => Maybe[Func[Num, Hash => Any]],
|
37
|
+
:thread_count => Maybe[Num]
|
36
38
|
})
|
37
39
|
|
38
40
|
@@StrictConfigHash = And[@@ConfigHash, lambda { |x|
|
@@ -47,19 +49,19 @@ module SnowplowTracker
|
|
47
49
|
Contract String, @@StrictConfigHash => lambda { |x| x.is_a? Emitter }
|
48
50
|
def initialize(endpoint, config={})
|
49
51
|
config = @@DefaultConfig.merge(config)
|
52
|
+
@lock = Monitor.new
|
50
53
|
@collector_uri = as_collector_uri(endpoint, config[:protocol], config[:port], config[:method])
|
51
54
|
@buffer = []
|
52
55
|
if not config[:buffer_size].nil?
|
53
56
|
@buffer_size = config[:buffer_size]
|
54
57
|
elsif config[:method] == 'get'
|
55
|
-
@buffer_size =
|
58
|
+
@buffer_size = 1
|
56
59
|
else
|
57
60
|
@buffer_size = 10
|
58
61
|
end
|
59
62
|
@method = config[:method]
|
60
63
|
@on_success = config[:on_success]
|
61
64
|
@on_failure = config[:on_failure]
|
62
|
-
@threads = []
|
63
65
|
LOGGER.info("#{self.class} initialized with endpoint #{@collector_uri}")
|
64
66
|
|
65
67
|
self
|
@@ -80,9 +82,11 @@ module SnowplowTracker
|
|
80
82
|
Contract Hash => nil
|
81
83
|
def input(payload)
|
82
84
|
payload.each { |k,v| payload[k] = v.to_s}
|
83
|
-
@
|
84
|
-
|
85
|
-
|
85
|
+
@lock.synchronize do
|
86
|
+
@buffer.push(payload)
|
87
|
+
if @buffer.size >= @buffer_size
|
88
|
+
flush
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
92
|
nil
|
@@ -91,58 +95,70 @@ module SnowplowTracker
|
|
91
95
|
# Flush the buffer
|
92
96
|
#
|
93
97
|
Contract Bool => nil
|
94
|
-
def flush(
|
95
|
-
|
96
|
-
|
98
|
+
def flush(async=true)
|
99
|
+
@lock.synchronize do
|
100
|
+
send_requests(@buffer)
|
101
|
+
@buffer = []
|
102
|
+
end
|
97
103
|
nil
|
98
104
|
end
|
99
105
|
|
100
106
|
# Send all events in the buffer to the collector
|
101
107
|
#
|
102
|
-
Contract
|
103
|
-
def send_requests
|
104
|
-
|
105
|
-
|
106
|
-
|
108
|
+
Contract ArrayOf[Hash] => nil
|
109
|
+
def send_requests(evts)
|
110
|
+
if evts.size < 1
|
111
|
+
LOGGER.info("Skipping sending events since buffer is empty")
|
112
|
+
return
|
113
|
+
end
|
114
|
+
LOGGER.info("Attempting to send #{evts.size} request#{evts.size == 1 ? '' : 's'}")
|
115
|
+
|
116
|
+
if @method == 'post'
|
117
|
+
post_succeeded = false
|
118
|
+
begin
|
119
|
+
request = http_post(SelfDescribingJson.new(
|
120
|
+
'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-2',
|
121
|
+
evts
|
122
|
+
).to_json)
|
123
|
+
post_succeeded = is_good_status_code(request.code)
|
124
|
+
rescue StandardError => se
|
125
|
+
LOGGER.warn(se)
|
126
|
+
end
|
127
|
+
if post_succeeded
|
128
|
+
unless @on_success.nil?
|
129
|
+
@on_success.call(evts.size)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
unless @on_failure.nil?
|
133
|
+
@on_failure.call(0, evts)
|
134
|
+
end
|
135
|
+
end
|
107
136
|
|
108
|
-
|
137
|
+
elsif @method == 'get'
|
109
138
|
success_count = 0
|
110
139
|
unsent_requests = []
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
140
|
+
evts.each do |evt|
|
141
|
+
get_succeeded = false
|
142
|
+
begin
|
143
|
+
request = http_get(evt)
|
144
|
+
get_succeeded = is_good_status_code(request.code)
|
145
|
+
rescue StandardError => se
|
146
|
+
LOGGER.warn(se)
|
117
147
|
end
|
118
|
-
if
|
119
|
-
|
120
|
-
@on_success.call(success_count)
|
121
|
-
end
|
148
|
+
if get_succeeded
|
149
|
+
success_count += 1
|
122
150
|
else
|
123
|
-
|
124
|
-
@on_failure.call(success_count, unsent_requests)
|
125
|
-
end
|
151
|
+
unsent_requests << evt
|
126
152
|
end
|
127
153
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
if request.code.to_i == 200
|
137
|
-
unless @on_success.nil?
|
138
|
-
@on_success.call(temp_buffer.size)
|
139
|
-
end
|
140
|
-
else
|
141
|
-
unless @on_failure.nil?
|
142
|
-
@on_failure.call(0, temp_buffer)
|
143
|
-
end
|
154
|
+
if unsent_requests.size == 0
|
155
|
+
unless @on_success.nil?
|
156
|
+
@on_success.call(success_count)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
unless @on_failure.nil?
|
160
|
+
@on_failure.call(success_count, unsent_requests)
|
144
161
|
end
|
145
|
-
|
146
162
|
end
|
147
163
|
end
|
148
164
|
|
@@ -162,7 +178,7 @@ module SnowplowTracker
|
|
162
178
|
http.use_ssl = true
|
163
179
|
end
|
164
180
|
response = http.request(request)
|
165
|
-
LOGGER.add(response.code
|
181
|
+
LOGGER.add(is_good_status_code(response.code) ? Logger::INFO : Logger::WARN) {
|
166
182
|
"GET request to #{@collector_uri} finished with status code #{response.code}"
|
167
183
|
}
|
168
184
|
|
@@ -184,13 +200,20 @@ module SnowplowTracker
|
|
184
200
|
request.body = payload.to_json
|
185
201
|
request.set_content_type('application/json; charset=utf-8')
|
186
202
|
response = http.request(request)
|
187
|
-
LOGGER.add(response.code
|
203
|
+
LOGGER.add(is_good_status_code(response.code) ? Logger::INFO : Logger::WARN) {
|
188
204
|
"POST request to #{@collector_uri} finished with status code #{response.code}"
|
189
205
|
}
|
190
206
|
|
191
207
|
response
|
192
208
|
end
|
193
209
|
|
210
|
+
# Only 2xx and 3xx status codes are considered successes
|
211
|
+
#
|
212
|
+
Contract String => Bool
|
213
|
+
def is_good_status_code(status_code)
|
214
|
+
status_code.to_i >= 200 && status_code.to_i < 400
|
215
|
+
end
|
216
|
+
|
194
217
|
private :as_collector_uri,
|
195
218
|
:http_get,
|
196
219
|
:http_post
|
@@ -200,27 +223,54 @@ module SnowplowTracker
|
|
200
223
|
|
201
224
|
class AsyncEmitter < Emitter
|
202
225
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
if sync
|
215
|
-
LOGGER.info('Starting synchronous flush')
|
216
|
-
@threads.each do |thread|
|
217
|
-
thread.join(10)
|
226
|
+
Contract String, @@StrictConfigHash => lambda { |x| x.is_a? Emitter }
|
227
|
+
def initialize(endpoint, config={})
|
228
|
+
@queue = Queue.new()
|
229
|
+
# @all_processed_condition and @results_unprocessed are used to emulate Python's Queue.task_done()
|
230
|
+
@queue.extend(MonitorMixin)
|
231
|
+
@all_processed_condition = @queue.new_cond
|
232
|
+
@results_unprocessed = 0
|
233
|
+
(config[:thread_count] || 1).times do
|
234
|
+
t = Thread.new do
|
235
|
+
consume
|
218
236
|
end
|
219
237
|
end
|
238
|
+
super(endpoint, config)
|
239
|
+
end
|
220
240
|
|
221
|
-
|
241
|
+
def consume
|
242
|
+
loop do
|
243
|
+
work_unit = @queue.pop
|
244
|
+
send_requests(work_unit)
|
245
|
+
@queue.synchronize do
|
246
|
+
@results_unprocessed -= 1
|
247
|
+
@all_processed_condition.broadcast
|
248
|
+
end
|
249
|
+
end
|
222
250
|
end
|
223
251
|
|
252
|
+
# Flush the buffer
|
253
|
+
# If async is false, block until the queue is empty
|
254
|
+
#
|
255
|
+
def flush(async=true)
|
256
|
+
loop do
|
257
|
+
@lock.synchronize do
|
258
|
+
@queue.synchronize do
|
259
|
+
@results_unprocessed += 1
|
260
|
+
end
|
261
|
+
@queue << @buffer
|
262
|
+
@buffer = []
|
263
|
+
end
|
264
|
+
if not async
|
265
|
+
LOGGER.info('Starting synchronous flush')
|
266
|
+
@queue.synchronize do
|
267
|
+
@all_processed_condition.wait_while { @results_unprocessed > 0 }
|
268
|
+
LOGGER.info('Finished synchronous flush')
|
269
|
+
end
|
270
|
+
end
|
271
|
+
break if @buffer.size < 1
|
272
|
+
end
|
273
|
+
end
|
224
274
|
end
|
225
275
|
|
226
276
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved.
|
2
|
+
#
|
3
|
+
# This program is licensed to you under the Apache License Version 2.0,
|
4
|
+
# and you may not use this file except in compliance with the Apache License Version 2.0.
|
5
|
+
# You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing,
|
8
|
+
# software distributed under the Apache License Version 2.0 is distributed on an
|
9
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
|
11
|
+
|
12
|
+
# Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com)
|
13
|
+
# Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd
|
14
|
+
# License:: Apache License Version 2.0
|
15
|
+
|
16
|
+
module SnowplowTracker
|
17
|
+
|
18
|
+
class SelfDescribingJson
|
19
|
+
|
20
|
+
def initialize(schema, data)
|
21
|
+
@schema = schema
|
22
|
+
@data = data
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
{
|
27
|
+
:schema => @schema,
|
28
|
+
:data => @data
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -14,12 +14,13 @@
|
|
14
14
|
# License:: Apache License Version 2.0
|
15
15
|
|
16
16
|
require 'contracts'
|
17
|
-
include Contracts
|
18
17
|
|
19
18
|
module SnowplowTracker
|
20
19
|
|
21
20
|
class Subject
|
22
21
|
|
22
|
+
include Contracts
|
23
|
+
|
23
24
|
@@default_platform = 'srv'
|
24
25
|
@@supported_platforms = ['pc', 'tv', 'mob', 'cnsl', 'iot']
|
25
26
|
|
@@ -52,6 +53,14 @@ module SnowplowTracker
|
|
52
53
|
self
|
53
54
|
end
|
54
55
|
|
56
|
+
# Set fingerprint for the user
|
57
|
+
#
|
58
|
+
Contract Num => Subject
|
59
|
+
def set_fingerprint(fingerprint)
|
60
|
+
@standard_nv_pairs['fp'] = fingerprint
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
55
64
|
# Set the screen resolution for a device
|
56
65
|
#
|
57
66
|
Contract Num, Num => Subject
|
@@ -15,14 +15,14 @@
|
|
15
15
|
|
16
16
|
require 'contracts'
|
17
17
|
require 'set'
|
18
|
-
include Contracts
|
19
|
-
|
20
18
|
require 'uuid'
|
21
19
|
|
22
20
|
module SnowplowTracker
|
23
21
|
|
24
22
|
class Tracker
|
25
23
|
|
24
|
+
include Contracts
|
25
|
+
|
26
26
|
@@EmitterInput = Or[lambda {|x| x.is_a? Emitter}, ArrayOf[lambda {|x| x.is_a? Emitter}]]
|
27
27
|
|
28
28
|
@@required_transaction_keys = Set.new(%w(order_id total_value))
|
@@ -55,22 +55,14 @@ module SnowplowTracker
|
|
55
55
|
augmented_item_keys.subset? @@recognised_augmented_item_keys
|
56
56
|
}
|
57
57
|
|
58
|
-
@@
|
59
|
-
schema: String,
|
60
|
-
data: Any
|
61
|
-
}, {
|
62
|
-
'schema' => String,
|
63
|
-
'data' => Any
|
64
|
-
}]
|
65
|
-
|
66
|
-
@@ContextsInput = ArrayOf[@@SelfDescribingJson]
|
58
|
+
@@ContextsInput = ArrayOf[SelfDescribingJson]
|
67
59
|
|
68
60
|
@@version = TRACKER_VERSION
|
69
61
|
@@default_encode_base64 = true
|
70
62
|
|
71
63
|
@@base_schema_path = "iglu:com.snowplowanalytics.snowplow"
|
72
64
|
@@schema_tag = "jsonschema"
|
73
|
-
@@context_schema = "#{@@base_schema_path}/contexts/#{@@schema_tag}/1-0-
|
65
|
+
@@context_schema = "#{@@base_schema_path}/contexts/#{@@schema_tag}/1-0-1"
|
74
66
|
@@unstruct_event_schema = "#{@@base_schema_path}/unstruct_event/#{@@schema_tag}/1-0-0"
|
75
67
|
|
76
68
|
Contract @@EmitterInput, Maybe[Subject], Maybe[String], Maybe[String], Bool => Tracker
|
@@ -119,12 +111,12 @@ module SnowplowTracker
|
|
119
111
|
|
120
112
|
# Builds a self-describing JSON from an array of custom contexts
|
121
113
|
#
|
122
|
-
Contract @@ContextsInput =>
|
114
|
+
Contract @@ContextsInput => Hash
|
123
115
|
def build_context(context)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
116
|
+
SelfDescribingJson.new(
|
117
|
+
@@context_schema,
|
118
|
+
context.map {|c| c.to_json}
|
119
|
+
).to_json
|
128
120
|
end
|
129
121
|
|
130
122
|
# Tracking methods
|
@@ -259,7 +251,8 @@ module SnowplowTracker
|
|
259
251
|
screen_view_properties['id'] = id
|
260
252
|
end
|
261
253
|
screen_view_schema = "#{@@base_schema_path}/screen_view/#{@@schema_tag}/1-0-0"
|
262
|
-
|
254
|
+
|
255
|
+
event_json = SelfDescribingJson.new(screen_view_schema, screen_view_properties)
|
263
256
|
|
264
257
|
self.track_unstruct_event(event_json, context, tstamp)
|
265
258
|
|
@@ -268,16 +261,14 @@ module SnowplowTracker
|
|
268
261
|
|
269
262
|
# Track an unstructured event
|
270
263
|
#
|
271
|
-
Contract
|
264
|
+
Contract SelfDescribingJson, Maybe[@@ContextsInput], Maybe[Num] => Tracker
|
272
265
|
def track_unstruct_event(event_json, context=nil, tstamp=nil)
|
273
266
|
pb = Payload.new
|
274
267
|
pb.add('e', 'ue')
|
275
268
|
|
276
|
-
envelope =
|
277
|
-
|
278
|
-
|
279
|
-
}
|
280
|
-
pb.add_json(envelope, @config['encode_base64'], 'ue_px', 'ue_pr')
|
269
|
+
envelope = SelfDescribingJson.new(@@unstruct_event_schema, event_json.to_json)
|
270
|
+
|
271
|
+
pb.add_json(envelope.to_json, @config['encode_base64'], 'ue_px', 'ue_pr')
|
281
272
|
|
282
273
|
unless context.nil?
|
283
274
|
pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co')
|
@@ -296,9 +287,9 @@ module SnowplowTracker
|
|
296
287
|
# Flush all events stored in all emitters
|
297
288
|
#
|
298
289
|
Contract Bool => Tracker
|
299
|
-
def flush(
|
290
|
+
def flush(async=false)
|
300
291
|
@emitters.each do |emitter|
|
301
|
-
emitter.flush(
|
292
|
+
emitter.flush(async)
|
302
293
|
end
|
303
294
|
|
304
295
|
self
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snowplow-tracker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Dean
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-08-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: contracts
|
@@ -17,14 +17,20 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ~>
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '0.
|
20
|
+
version: '0.7'
|
21
|
+
- - <=
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0.11'
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
25
28
|
- - ~>
|
26
29
|
- !ruby/object:Gem::Version
|
27
|
-
version: '0.
|
30
|
+
version: '0.7'
|
31
|
+
- - <=
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.11'
|
28
34
|
- !ruby/object:Gem::Dependency
|
29
35
|
name: uuid
|
30
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +86,7 @@ files:
|
|
80
86
|
- lib/snowplow-tracker/contracts.rb
|
81
87
|
- lib/snowplow-tracker/emitters.rb
|
82
88
|
- lib/snowplow-tracker/payload.rb
|
89
|
+
- lib/snowplow-tracker/self_describing_json.rb
|
83
90
|
- lib/snowplow-tracker/subject.rb
|
84
91
|
- lib/snowplow-tracker/tracker.rb
|
85
92
|
- lib/snowplow-tracker/version.rb
|
@@ -103,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
110
|
version: '0'
|
104
111
|
requirements: []
|
105
112
|
rubyforge_project:
|
106
|
-
rubygems_version: 2.4.
|
113
|
+
rubygems_version: 2.4.8
|
107
114
|
signing_key:
|
108
115
|
specification_version: 4
|
109
116
|
summary: Ruby Analytics for Snowplow
|