snowplow-tracker 0.7.0.pre.alpha.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +41 -16
- data/lib/snowplow-tracker/emitters.rb +339 -154
- data/lib/snowplow-tracker/page.rb +55 -0
- data/lib/snowplow-tracker/payload.rb +28 -39
- data/lib/snowplow-tracker/self_describing_json.rb +92 -9
- data/lib/snowplow-tracker/subject.rb +289 -68
- data/lib/snowplow-tracker/timestamp.rb +93 -25
- data/lib/snowplow-tracker/tracker.rb +521 -257
- data/lib/snowplow-tracker/version.rb +26 -4
- data/lib/snowplow-tracker.rb +5 -5
- metadata +12 -33
- data/lib/snowplow-tracker/contracts.rb +0 -29
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2013-
|
1
|
+
# Copyright (c) 2013-2021 Snowplow Analytics Ltd. All rights reserved.
|
2
2
|
#
|
3
3
|
# This program is licensed to you under the Apache License Version 2.0,
|
4
4
|
# and you may not use this file except in compliance with the Apache License Version 2.0.
|
@@ -9,335 +9,596 @@
|
|
9
9
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
10
|
# See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
|
11
11
|
|
12
|
-
# Author::
|
13
|
-
# Copyright:: Copyright (c) 2013-
|
12
|
+
# Author:: Snowplow Analytics Ltd
|
13
|
+
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
|
14
14
|
# License:: Apache License Version 2.0
|
15
15
|
|
16
|
-
|
16
|
+
|
17
17
|
require 'securerandom'
|
18
18
|
require 'set'
|
19
19
|
|
20
20
|
module SnowplowTracker
|
21
|
-
|
21
|
+
# Allows the tracking of events. The tracker accepts event properties to its
|
22
|
+
# various `track_x_event` methods, and creates an appropriate event payload.
|
23
|
+
# This payload is passed to one or more Emitters for sending to the event
|
24
|
+
# collector.
|
25
|
+
#
|
26
|
+
# A Tracker is always associated with one {Subject}, and one or more
|
27
|
+
# {Emitter}. The Subject object stores information about the user, and will be
|
28
|
+
# generated automatically if one is not provided during initialization. It can
|
29
|
+
# be swapped out for another Subject using {#set_subject}.
|
30
|
+
#
|
31
|
+
# Tracker objects can access the methods of their associated {Subject}, e.g.
|
32
|
+
# {#set_user_id}.
|
33
|
+
#
|
34
|
+
# The Emitter, or an array of Emitters, must be given during initialization.
|
35
|
+
# They will send the prepared events to the event collector. It's possible to
|
36
|
+
# add further Emitters to an existing Tracker, using {#add_emitter}. However,
|
37
|
+
# Emitters cannot be removed from Trackers.
|
38
|
+
#
|
39
|
+
# At initialization, two Tracker parameters can be set which will be added to
|
40
|
+
# all events. The first is the Tracker namespace. This is especially useful to
|
41
|
+
# distinguish between events from different Trackers, if more than one is
|
42
|
+
# being used. The namespace value will be sent as the `tna` field in the raw
|
43
|
+
# event, mapping to `name_tracker` in the processed event.
|
44
|
+
#
|
45
|
+
# The second user-set Tracker property is the app ID (`aid`; `app_id`). This
|
46
|
+
# is the unique identifier for the site or application, and is particularly
|
47
|
+
# useful for distinguishing between events when Snowplow tracking has been
|
48
|
+
# implemented in multiple apps.
|
49
|
+
#
|
50
|
+
# The final initialization parameter is a setting for the base64-encoding of
|
51
|
+
# any JSONs in the event payload. These will be the {SelfDescribingJson}s used
|
52
|
+
# to provide context to events, or in the {#track_self_describing_event}
|
53
|
+
# method. The default is for JSONs to be encoded. Once the Tracker has been
|
54
|
+
# instantiated, it is not possible to change this setting.
|
55
|
+
#
|
56
|
+
# # Tracking events
|
57
|
+
#
|
58
|
+
# The Tracker `#track_x_event` methods all work similarly. An event payload is
|
59
|
+
# created containing the relevant properties, which is passed to an {Emitter}
|
60
|
+
# for sending. All payloads have a unique event ID (`event_id`) added to them
|
61
|
+
# (a type-4 UUID created using the SecureRandom module). This is sent as the
|
62
|
+
# `eid` field in the raw event.
|
63
|
+
#
|
64
|
+
# The Ruby tracker provides the ability to track multiple types of events
|
65
|
+
# out-of-the-box. The `#track_x_event` methods range from single purpose
|
66
|
+
# methods, such as {#track_page_view}, to the more complex but flexible
|
67
|
+
# {#track_self_describing_event}, which can be used to track any kind of
|
68
|
+
# event. We strongly recommend using {#track_self_describing_event} for your
|
69
|
+
# tracking, as it allows you to design custom event types to match your
|
70
|
+
# business requirements.
|
71
|
+
#
|
72
|
+
# This table gives the event type in the raw and processed events, defined in
|
73
|
+
# the Snowplow Tracker Protocol. This is the `e` or `event` parameter. Note
|
74
|
+
# that {#track_screen_view} calls {#track_self_describing_event} behind the
|
75
|
+
# scenes, resulting in a `ue` event type.
|
76
|
+
#
|
77
|
+
# <br>
|
78
|
+
#
|
79
|
+
# | Tracker method | `e` (raw) | `event` (processed) |
|
80
|
+
# | --- | --- | --- |
|
81
|
+
# | {#track_self_describing_event} | `ue` | `unstruct` |
|
82
|
+
# | {#track_struct_event} | `se` | `struct` |
|
83
|
+
# | {#track_page_view} | `pv` | `page_view` |
|
84
|
+
# | {#track_ecommerce_transaction} | `tr` and `ti` | `transaction` and `transaction_item` |
|
85
|
+
# | {#track_screen_view} | `ue` | `unstruct` |
|
86
|
+
#
|
87
|
+
# <br>
|
88
|
+
#
|
89
|
+
# The name `ue`, "unstructured event", is partially depreciated. This event
|
90
|
+
# type was originally created as a counterpart to "structured event", but the
|
91
|
+
# name is misleading. An `unstruct` event requires a schema ruleset and
|
92
|
+
# therefore can be considered more structured than a `struct` event. We prefer
|
93
|
+
# the name "self-describing event", after the {SelfDescribingJson} schema.
|
94
|
+
# Changing the event name in the Tracker Protocol would be a breaking change,
|
95
|
+
# so for now the self-describing events are still sent as "unstruct".
|
96
|
+
#
|
97
|
+
# All the `#track_x_event` methods share common features and parameters. Every
|
98
|
+
# type of event can have an optional context, {Subject}, and {Page} added. A
|
99
|
+
# {Timestamp} can also be provided for all event types to override the default
|
100
|
+
# event timestamp.
|
101
|
+
#
|
102
|
+
# [Event
|
103
|
+
# context](https://docs.snowplowanalytics.com/docs/understanding-tracking-design/understanding-events-entities/)
|
104
|
+
# can be provided as an array of {SelfDescribingJson}. Each element of the
|
105
|
+
# array is called an entity. Contextual entities can be used to describe the
|
106
|
+
# setting in which the event occurred. For example, a "user" entity could be
|
107
|
+
# created and attached to all events from each user. For a search event,
|
108
|
+
# entities could be attached for each of the search results. The Ruby tracker
|
109
|
+
# does not automatically add any event context. This is in contrast to the
|
110
|
+
# [Snowplow JavaScript Tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/),
|
111
|
+
# which automatically attaches a "webpage" entity to every event that it tracks,
|
112
|
+
# containing a unique ID for that loaded page.
|
113
|
+
#
|
114
|
+
# @see Subject
|
115
|
+
# @see Emitter
|
116
|
+
# @see
|
117
|
+
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol
|
118
|
+
# the Snowplow Tracker Protocol
|
119
|
+
# @see
|
120
|
+
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/tracking-events/
|
121
|
+
# the Snowplow docs page about tracking events
|
122
|
+
# @see
|
123
|
+
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/enriching-your-events/
|
124
|
+
# the Snowplow docs page about adding context and other extra data to events
|
125
|
+
# @see
|
126
|
+
# https://docs.snowplowanalytics.com/docs/understanding-tracking-design/introduction-to-tracking-design/
|
127
|
+
# introduction to Snowplow tracking design
|
128
|
+
# @api public
|
22
129
|
class Tracker
|
130
|
+
# @!group Public constants
|
23
131
|
|
24
|
-
|
25
|
-
|
26
|
-
@@EmitterInput = Or[lambda {|x| x.is_a? Emitter}, ArrayOf[lambda {|x| x.is_a? Emitter}]]
|
27
|
-
|
28
|
-
@@required_transaction_keys = Set.new(%w(order_id total_value))
|
29
|
-
@@recognised_transaction_keys = Set.new(%w(order_id total_value affiliation tax_value shipping city state country currency))
|
30
|
-
|
31
|
-
@@Transaction = lambda { |x|
|
32
|
-
return false unless x.class == Hash
|
33
|
-
transaction_keys = Set.new(x.keys)
|
34
|
-
@@required_transaction_keys.subset? transaction_keys and
|
35
|
-
transaction_keys.subset? @@recognised_transaction_keys
|
36
|
-
}
|
37
|
-
|
38
|
-
@@required_item_keys = Set.new(%w(sku price quantity))
|
39
|
-
@@recognised_item_keys = Set.new(%w(sku price quantity name category context))
|
132
|
+
# SelfDescribingJson objects are sent encoded by default
|
133
|
+
DEFAULT_ENCODE_BASE64 = true
|
40
134
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
}
|
135
|
+
# @private
|
136
|
+
BASE_SCHEMA_PATH = 'iglu:com.snowplowanalytics.snowplow'
|
137
|
+
# @private
|
138
|
+
SCHEMA_TAG = 'jsonschema'
|
139
|
+
# @private
|
140
|
+
CONTEXT_SCHEMA = "#{BASE_SCHEMA_PATH}/contexts/#{SCHEMA_TAG}/1-0-1"
|
141
|
+
# @private
|
142
|
+
UNSTRUCT_EVENT_SCHEMA = "#{BASE_SCHEMA_PATH}/unstruct_event/#{SCHEMA_TAG}/1-0-0"
|
143
|
+
# @private
|
144
|
+
SCREEN_VIEW_SCHEMA = "#{BASE_SCHEMA_PATH}/screen_view/#{SCHEMA_TAG}/1-0-0"
|
47
145
|
|
48
|
-
|
49
|
-
@@recognised_augmented_item_keys = Set.new(%w(sku price quantity name category context tstamp order_id currency))
|
146
|
+
# @!endgroup
|
50
147
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
148
|
+
# Create a new Tracker. `emitters` is the only strictly required parameter.
|
149
|
+
#
|
150
|
+
# @param emitters [Emitter, Array<Emitter>] one or more Emitter objects
|
151
|
+
# @param subject [Subject] a Subject object
|
152
|
+
# @param namespace [String] a name for the Tracker
|
153
|
+
# @param app_id [String] the app ID
|
154
|
+
# @param encode_base64 [Bool] whether JSONs will be base64-encoded or not
|
155
|
+
# @example Initializing a Tracker with all possible options
|
156
|
+
# SnowplowTracker::Tracker.new(
|
157
|
+
# emitters: SnowplowTracker::Emitter.new(endpoint: 'collector.example.com'),
|
158
|
+
# subject: SnowplowTracker::Subject.new,
|
159
|
+
# namespace: 'tracker_no_encode',
|
160
|
+
# app_id: 'rails_main',
|
161
|
+
# encode_base64: false
|
162
|
+
# )
|
163
|
+
# @api public
|
164
|
+
#
|
165
|
+
# @note All the Tracker instance methods return the Tracker object, allowing
|
166
|
+
# method chaining, e.g.
|
167
|
+
# `SnowplowTracker::Tracker.new.set_user_id('12345').track_page_view(page_url: 'www.example.com`
|
168
|
+
def initialize(emitters:, subject: nil, namespace: nil, app_id: nil, encode_base64: DEFAULT_ENCODE_BASE64)
|
70
169
|
@emitters = Array(emitters)
|
71
|
-
if subject.nil?
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@
|
170
|
+
@subject = if subject.nil?
|
171
|
+
Subject.new
|
172
|
+
else
|
173
|
+
subject
|
174
|
+
end
|
175
|
+
@settings = {
|
77
176
|
'tna' => namespace,
|
78
|
-
'tv' =>
|
177
|
+
'tv' => TRACKER_VERSION,
|
79
178
|
'aid' => app_id
|
80
179
|
}
|
81
|
-
@
|
82
|
-
'encode_base64' => encode_base64
|
83
|
-
}
|
84
|
-
|
85
|
-
self
|
180
|
+
@encode_base64 = encode_base64
|
86
181
|
end
|
87
182
|
|
88
|
-
#
|
89
|
-
#
|
183
|
+
# @!method set_color_depth(depth)
|
184
|
+
# call {Subject#set_color_depth}
|
185
|
+
# @!method set_domain_session_id(sid)
|
186
|
+
# call {Subject#set_domain_session_id}
|
187
|
+
# @!method set_domain_session_idx(vid)
|
188
|
+
# call {Subject#set_domain_session_idx}
|
189
|
+
# @!method set_domain_user_id(duid)
|
190
|
+
# call {Subject#set_domain_user_id}
|
191
|
+
# @!method set_fingerprint(fingerprint)
|
192
|
+
# call {Subject#set_fingerprint}
|
193
|
+
# @!method set_ip_address(ip)
|
194
|
+
# call {Subject#set_ip_address}
|
195
|
+
# @!method set_lang(lang)
|
196
|
+
# call {Subject#set_lang}
|
197
|
+
# @!method set_network_user_id(nuid)
|
198
|
+
# call {Subject#set_network_user_id}
|
199
|
+
# @!method set_platform(platform)
|
200
|
+
# call {Subject#set_platform}
|
201
|
+
# @!method set_screen_resolution(width:, height:)
|
202
|
+
# call {Subject#set_screen_resolution}
|
203
|
+
# @!method set_timezone(timezone)
|
204
|
+
# call {Subject#set_timezone}
|
205
|
+
# @!method set_user_id(user_id)
|
206
|
+
# call {Subject#set_user_id}
|
207
|
+
# @!method set_useragent(useragent)
|
208
|
+
# call {Subject#set_useragent}
|
209
|
+
# @!method set_viewport(width:, height:)
|
210
|
+
# call {Subject#set_viewport}
|
90
211
|
Subject.instance_methods(false).each do |name|
|
91
|
-
|
92
|
-
|
212
|
+
if RUBY_VERSION >= '3.0.0'
|
213
|
+
define_method name, ->(*args, **kwargs) do
|
214
|
+
@subject.method(name.to_sym).call(*args, **kwargs)
|
93
215
|
|
94
|
-
|
216
|
+
self
|
217
|
+
end
|
218
|
+
else
|
219
|
+
define_method name, ->(*args) do
|
220
|
+
@subject.method(name.to_sym).call(*args)
|
221
|
+
|
222
|
+
self
|
223
|
+
end
|
95
224
|
end
|
96
225
|
end
|
97
226
|
|
98
227
|
# Generates a type-4 UUID to identify this event
|
99
|
-
|
100
|
-
def
|
228
|
+
# @private
|
229
|
+
def event_id
|
101
230
|
SecureRandom.uuid
|
102
231
|
end
|
103
232
|
|
104
|
-
#
|
105
|
-
#
|
106
|
-
Contract nil => Num
|
107
|
-
def get_timestamp
|
108
|
-
(Time.now.to_f * 1000).to_i
|
109
|
-
end
|
110
|
-
|
111
|
-
# Builds a self-describing JSON from an array of custom contexts
|
112
|
-
#
|
113
|
-
Contract @@ContextsInput => Hash
|
233
|
+
# Builds a single self-describing JSON from an array of custom contexts
|
234
|
+
# @private
|
114
235
|
def build_context(context)
|
115
236
|
SelfDescribingJson.new(
|
116
|
-
|
117
|
-
context.map
|
118
|
-
|
237
|
+
CONTEXT_SCHEMA,
|
238
|
+
context.map(&:to_json)
|
239
|
+
).to_json
|
119
240
|
end
|
120
241
|
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
#
|
126
|
-
Contract Payload => nil
|
127
|
-
def track(pb)
|
128
|
-
pb.add_dict(@subject.standard_nv_pairs)
|
129
|
-
pb.add_dict(@standard_nv_pairs)
|
130
|
-
pb.add('eid', get_event_id())
|
131
|
-
@emitters.each{ |emitter| emitter.input(pb.context)}
|
242
|
+
# Sends the payload hash as a request to the Emitter(s)
|
243
|
+
# @private
|
244
|
+
def track(payload)
|
245
|
+
@emitters.each { |emitter| emitter.input(payload.data) }
|
132
246
|
|
133
247
|
nil
|
134
248
|
end
|
135
249
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
|
139
|
-
def
|
140
|
-
if tstamp.nil?
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
track_page_view(page_url, page_title, referrer, context, DeviceTimestamp.new(tstamp))
|
250
|
+
# Ensures that either a DeviceTimestamp or TrueTimestamp is associated with
|
251
|
+
# every event.
|
252
|
+
# @private
|
253
|
+
def process_tstamp(tstamp)
|
254
|
+
tstamp = Timestamp.create if tstamp.nil?
|
255
|
+
tstamp = DeviceTimestamp.new(tstamp) if tstamp.is_a? Numeric
|
256
|
+
tstamp
|
145
257
|
end
|
146
258
|
|
147
|
-
#
|
259
|
+
# Attaches the more generic fields to the event payload. This includes
|
260
|
+
# context, Subject, and Page if they are present. The timestamp is added, as
|
261
|
+
# well as all fields from `@settings`.
|
148
262
|
#
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
263
|
+
# Finally, the Tracker generates and attaches an event ID.
|
264
|
+
# @private
|
265
|
+
def finalise_payload(payload, context, tstamp, event_subject, page)
|
266
|
+
payload.add_json(build_context(context), @encode_base64, 'cx', 'co') unless context.nil? || context.empty?
|
267
|
+
payload.add_hash(page.details) unless page.nil?
|
268
|
+
|
269
|
+
if event_subject.nil?
|
270
|
+
payload.add_hash(@subject.details)
|
271
|
+
else
|
272
|
+
payload.add_hash(@subject.details.merge(event_subject.details))
|
159
273
|
end
|
160
274
|
|
161
|
-
|
162
|
-
|
163
|
-
|
275
|
+
payload.add(tstamp.type, tstamp.value)
|
276
|
+
payload.add_hash(@settings)
|
277
|
+
payload.add('eid', event_id)
|
164
278
|
|
165
|
-
|
279
|
+
nil
|
166
280
|
end
|
167
281
|
|
168
|
-
# Track a
|
169
|
-
#
|
170
|
-
#
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
track(pb)
|
282
|
+
# Track a visit to a page.
|
283
|
+
# @example
|
284
|
+
# SnowplowTracker::Tracker.new.track_page_view(page_url: 'www.example.com',
|
285
|
+
# page_title: 'example',
|
286
|
+
# referrer: 'www.referrer.com')
|
287
|
+
#
|
288
|
+
# @param page_url [String] the URL of the page
|
289
|
+
# @param page_title [String] the page title
|
290
|
+
# @param referrer [String] the URL of the referrer page
|
291
|
+
# @param context [Array<SelfDescribingJson>] an array of SelfDescribingJson objects
|
292
|
+
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
|
293
|
+
# @param subject [Subject] event-specific Subject object
|
294
|
+
# @param page [Page] override the page_url, page_title, or referrer
|
295
|
+
#
|
296
|
+
# @api public
|
297
|
+
def track_page_view(page_url:, page_title: nil, referrer: nil,
|
298
|
+
context: nil, tstamp: nil, subject: nil, page: nil)
|
299
|
+
tstamp = process_tstamp(tstamp)
|
187
300
|
|
188
|
-
|
189
|
-
|
301
|
+
payload = Payload.new
|
302
|
+
payload.add('e', 'pv')
|
303
|
+
payload.add('url', page_url)
|
304
|
+
payload.add('page', page_title)
|
305
|
+
payload.add('refr', referrer)
|
190
306
|
|
191
|
-
|
192
|
-
|
193
|
-
Contract @@Transaction, ArrayOf[@@Item], Maybe[@@ContextsInput], Maybe[Num] => Tracker
|
194
|
-
def track_ecommerce_transaction(transaction,
|
195
|
-
items,
|
196
|
-
context=nil,
|
197
|
-
tstamp=nil)
|
198
|
-
if tstamp.nil?
|
199
|
-
tstamp = get_timestamp
|
200
|
-
end
|
307
|
+
finalise_payload(payload, context, tstamp, subject, page)
|
308
|
+
track(payload)
|
201
309
|
|
202
|
-
|
310
|
+
self
|
203
311
|
end
|
204
312
|
|
205
|
-
# Track an
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
313
|
+
# Track an eCommerce transaction, and all the items in it.
|
314
|
+
#
|
315
|
+
# This method is unique in sending multiple events: one `transaction` event,
|
316
|
+
# and one `transaction_item` event for each item. If Subject or Page objects
|
317
|
+
# are provided, their parameters will be merged into both `transaction` and
|
318
|
+
# `transaction_item` events. The timestamp and event ID of the
|
319
|
+
# `transaction_item` events will always be the same as the `transaction`.
|
320
|
+
# Transaction items are also automatically populated with the `order_id` and
|
321
|
+
# `currency` fields from the transaction.
|
322
|
+
#
|
323
|
+
# Event context is handled differently for `transaction` and
|
324
|
+
# `transaction_item` events. A context array argument provided to this
|
325
|
+
# method will be attached to the `transaction` event only. To attach a
|
326
|
+
# context array to a transaction item, use the key "context" in the item
|
327
|
+
# hash.
|
328
|
+
#
|
329
|
+
# The transaction and item hash arguments must contain the correct keys, as
|
330
|
+
# shown in the tables below.
|
331
|
+
#
|
332
|
+
# | Transaction fields | Description | Required? | Type |
|
333
|
+
# | --- | --- | --- | --- |
|
334
|
+
# | order_id | ID of the eCommerce transaction | Yes | String |
|
335
|
+
# | total_value | Total transaction value | Yes | Num |
|
336
|
+
# | affiliation | Transaction affiliation | No | String |
|
337
|
+
# | tax_value | Transaction tax value | No | Num |
|
338
|
+
# | shipping | Delivery cost charged | No | Num |
|
339
|
+
# | city | Delivery address city | No | String |
|
340
|
+
# | state | Delivery address state | No | String |
|
341
|
+
# | country | Delivery address country | No | String |
|
342
|
+
# | currency | Transaction currency | No | String |
|
343
|
+
#
|
344
|
+
# <br>
|
345
|
+
#
|
346
|
+
# | Item fields | Description | Required? | Type |
|
347
|
+
# | --- | --- | --- | --- |
|
348
|
+
# | sku | Item SKU | Yes | String |
|
349
|
+
# | price | Item price | Yes | Num |
|
350
|
+
# | quantity | Item quantity | Yes | Integer |
|
351
|
+
# | name | Item name | No | String |
|
352
|
+
# | category | Item category | No | String |
|
353
|
+
# | context | Item event context | No | Array[{SelfDescribingJson}] |
|
354
|
+
#
|
355
|
+
# @example Tracking a transaction containing two items
|
356
|
+
# SnowplowTracker::Tracker.new.track_ecommerce_transaction(
|
357
|
+
# transaction: {
|
358
|
+
# 'order_id' => '12345',
|
359
|
+
# 'total_value' => 49.99,
|
360
|
+
# 'affiliation' => 'my_company',
|
361
|
+
# 'tax_value' => 0,
|
362
|
+
# 'shipping' => 0,
|
363
|
+
# 'city' => 'Phoenix',
|
364
|
+
# 'state' => 'Arizona',
|
365
|
+
# 'country' => 'USA',
|
366
|
+
# 'currency' => 'USD'
|
367
|
+
# },
|
368
|
+
# items: [
|
369
|
+
# {
|
370
|
+
# 'sku' => 'pbz0026',
|
371
|
+
# 'price' => 19.99,
|
372
|
+
# 'quantity' => 1
|
373
|
+
# },
|
374
|
+
# {
|
375
|
+
# 'sku' => 'pbz0038',
|
376
|
+
# 'price' => 15,
|
377
|
+
# 'quantity' => 2,
|
378
|
+
# 'name' => 'crystals',
|
379
|
+
# 'category' => 'magic'
|
380
|
+
# }
|
381
|
+
# ]
|
382
|
+
# )
|
383
|
+
#
|
384
|
+
# @param transaction [Hash] the correctly structured transaction hash
|
385
|
+
# @param items [Array<Hash>] an array of correctly structured item hashes
|
386
|
+
# @param context [Array<SelfDescribingJson>] an array of SelfDescribingJson objects
|
387
|
+
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
|
388
|
+
# @param subject [Subject] event-specific Subject object
|
389
|
+
# @param page [Page] event-specific Page object
|
390
|
+
#
|
391
|
+
# @api public
|
392
|
+
def track_ecommerce_transaction(transaction:, items:,
|
393
|
+
context: nil, tstamp: nil,
|
394
|
+
subject: nil, page: nil)
|
395
|
+
tstamp = process_tstamp(tstamp)
|
396
|
+
|
397
|
+
transform_keys(transaction)
|
398
|
+
|
399
|
+
payload = Payload.new
|
400
|
+
payload.add('e', 'tr')
|
401
|
+
payload.add('tr_id', transaction['order_id'])
|
402
|
+
payload.add('tr_tt', transaction['total_value'])
|
403
|
+
payload.add('tr_af', transaction['affiliation'])
|
404
|
+
payload.add('tr_tx', transaction['tax_value'])
|
405
|
+
payload.add('tr_sh', transaction['shipping'])
|
406
|
+
payload.add('tr_ci', transaction['city'])
|
407
|
+
payload.add('tr_st', transaction['state'])
|
408
|
+
payload.add('tr_co', transaction['country'])
|
409
|
+
payload.add('tr_cu', transaction['currency'])
|
410
|
+
|
411
|
+
finalise_payload(payload, context, tstamp, subject, page)
|
412
|
+
|
413
|
+
track(payload)
|
414
|
+
|
415
|
+
items.each do |item|
|
416
|
+
transform_keys(item)
|
230
417
|
item['tstamp'] = tstamp
|
231
418
|
item['order_id'] = transaction['order_id']
|
232
419
|
item['currency'] = transaction['currency']
|
233
|
-
track_ecommerce_transaction_item(item)
|
420
|
+
track_ecommerce_transaction_item(item, subject, page)
|
234
421
|
end
|
235
422
|
|
236
423
|
self
|
237
424
|
end
|
238
425
|
|
239
|
-
#
|
240
|
-
#
|
241
|
-
|
242
|
-
def
|
243
|
-
|
244
|
-
tstamp = get_timestamp
|
245
|
-
end
|
246
|
-
|
247
|
-
track_struct_event(category, action, label, property, value, context, DeviceTimestamp.new(tstamp))
|
426
|
+
# Makes sure all hash keys are strings rather than symbols.
|
427
|
+
# The Ruby core language added a method for this in Ruby 2.5.
|
428
|
+
# @private
|
429
|
+
def transform_keys(hash)
|
430
|
+
hash.keys.each { |key| hash[key.to_s] = hash.delete key }
|
248
431
|
end
|
249
|
-
# Track a structured event
|
250
|
-
#
|
251
|
-
Contract String, String, Maybe[String], Maybe[String], Maybe[Num], Maybe[@@ContextsInput], Timestamp => Tracker
|
252
|
-
def track_struct_event(category, action, label=nil, property=nil, value=nil, context=nil, tstamp=nil)
|
253
|
-
pb = Payload.new
|
254
|
-
pb.add('e', 'se')
|
255
|
-
pb.add('se_ca', category)
|
256
|
-
pb.add('se_ac', action)
|
257
|
-
pb.add('se_la', label)
|
258
|
-
pb.add('se_pr', property)
|
259
|
-
pb.add('se_va', value)
|
260
|
-
unless context.nil?
|
261
|
-
pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co')
|
262
|
-
end
|
263
432
|
|
264
|
-
|
265
|
-
|
433
|
+
# Track a single item within an ecommerce transaction.
|
434
|
+
# @private
|
435
|
+
def track_ecommerce_transaction_item(details, subject, page)
|
436
|
+
payload = Payload.new
|
437
|
+
payload.add('e', 'ti')
|
438
|
+
payload.add('ti_id', details['order_id'])
|
439
|
+
payload.add('ti_sk', details['sku'])
|
440
|
+
payload.add('ti_pr', details['price'])
|
441
|
+
payload.add('ti_qu', details['quantity'])
|
442
|
+
payload.add('ti_nm', details['name'])
|
443
|
+
payload.add('ti_ca', details['category'])
|
444
|
+
payload.add('ti_cu', details['currency'])
|
445
|
+
|
446
|
+
finalise_payload(payload, details['context'], details['tstamp'], subject, page)
|
447
|
+
track(payload)
|
266
448
|
|
267
449
|
self
|
268
450
|
end
|
269
451
|
|
270
|
-
# Track a
|
452
|
+
# Track a structured event. `category` and `action` are required.
|
271
453
|
#
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
454
|
+
# This event type can be used to track many types of user activity, as it is
|
455
|
+
# somewhat customizable. This event type is provided particularly for
|
456
|
+
# concordance with Google Analytics tracking, where events are structured by
|
457
|
+
# "category", "action", "label", and "value".
|
458
|
+
#
|
459
|
+
# For fully customizable event tracking, we recommend you use
|
460
|
+
# self-describing events.
|
461
|
+
#
|
462
|
+
# @example
|
463
|
+
# SnowplowTracker::Tracker.new.track_struct_event(
|
464
|
+
# category: 'shop',
|
465
|
+
# action: 'add-to-basket',
|
466
|
+
# property: 'pcs',
|
467
|
+
# value: 2
|
468
|
+
# )
|
469
|
+
#
|
470
|
+
#
|
471
|
+
# @see #track_self_describing_event
|
472
|
+
#
|
473
|
+
# @param category [String] the event category
|
474
|
+
# @param action [String] the action performed
|
475
|
+
# @param label [String] an event label
|
476
|
+
# @param property [String] an event property
|
477
|
+
# @param value [Num] a value for the event
|
478
|
+
# @param context [Array<SelfDescribingJson>] an array of SelfDescribingJson objects
|
479
|
+
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
|
480
|
+
# @param subject [Subject] event-specific Subject object
|
481
|
+
# @param page [Page] event-specific Page object
|
482
|
+
#
|
483
|
+
# @api public
|
484
|
+
def track_struct_event(category:, action:, label: nil, property: nil,
|
485
|
+
value: nil, context: nil, tstamp: nil, subject: nil, page: nil)
|
486
|
+
tstamp = process_tstamp(tstamp)
|
487
|
+
|
488
|
+
payload = Payload.new
|
489
|
+
payload.add('e', 'se')
|
490
|
+
payload.add('se_ca', category)
|
491
|
+
payload.add('se_ac', action)
|
492
|
+
payload.add('se_la', label)
|
493
|
+
payload.add('se_pr', property)
|
494
|
+
payload.add('se_va', value)
|
495
|
+
|
496
|
+
finalise_payload(payload, context, tstamp, subject, page)
|
497
|
+
track(payload)
|
286
498
|
|
287
499
|
self
|
288
500
|
end
|
289
501
|
|
290
|
-
#
|
502
|
+
# Track a screen view event. Note that while the `name` and `id` parameters
|
503
|
+
# are both optional, you must provided at least one of them to create a
|
504
|
+
# valid event.
|
291
505
|
#
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
#
|
298
|
-
#
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
506
|
+
# This method creates an `unstruct` event, by creating a
|
507
|
+
# {SelfDescribingJson} and calling {#track_self_describing_event}. The
|
508
|
+
# schema ID for this is
|
509
|
+
# "iglu:com.snowplowanalytics.snowplow/screen_view/jsonschema/1-0-0", and
|
510
|
+
# the data field will contain the name and/or ID.
|
511
|
+
#
|
512
|
+
# @example
|
513
|
+
# SnowplowTracker::Tracker.new.track_screen_view(name: 'HUD > Save Game',
|
514
|
+
# id: 'screen23')
|
515
|
+
#
|
516
|
+
#
|
517
|
+
# @see #track_page_view
|
518
|
+
# @see #track_self_describing_event
|
519
|
+
#
|
520
|
+
# @param name [String] the screen name (human readable)
|
521
|
+
# @param id [String] the unique screen ID
|
522
|
+
# @param context [Array<SelfDescribingJson>] an array of SelfDescribingJson objects
|
523
|
+
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
|
524
|
+
# @param subject [Subject] event-specific Subject object
|
525
|
+
# @param page [Page] event-specific Page object
|
526
|
+
#
|
527
|
+
# @api public
|
528
|
+
def track_screen_view(name: nil, id: nil, context: nil, tstamp: nil, subject: nil, page: nil)
|
529
|
+
screen_view_properties = {}
|
530
|
+
screen_view_properties['name'] = name unless name.nil?
|
531
|
+
screen_view_properties['id'] = id unless id.nil?
|
303
532
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
def track_unstruct_event(event_json, context=nil, tstamp=nil)
|
308
|
-
if tstamp.nil?
|
309
|
-
tstamp = get_timestamp
|
310
|
-
end
|
533
|
+
event_json = SelfDescribingJson.new(SCREEN_VIEW_SCHEMA, screen_view_properties)
|
534
|
+
track_unstruct_event(event_json: event_json, context: context,
|
535
|
+
tstamp: tstamp, subject: subject, page: page)
|
311
536
|
|
312
|
-
|
537
|
+
self
|
313
538
|
end
|
314
539
|
|
315
|
-
# Track
|
540
|
+
# Track a self-describing event. These are custom events based on
|
541
|
+
# {SelfDescribingJson}, i.e. a JSON schema and a defined set of properties.
|
542
|
+
#
|
543
|
+
# This is useful for tracking specific or proprietary event types, or events
|
544
|
+
# with unpredicable or frequently changing properties.
|
545
|
+
#
|
546
|
+
# This method creates an `unstruct` event type. It is actually an alias for
|
547
|
+
# {#track_unstruct_event}, which is depreciated due to its unhelpful name.
|
548
|
+
#
|
549
|
+
# @example
|
550
|
+
# self_desc_json = SnowplowTracker::SelfDescribingJson.new(
|
551
|
+
# "iglu:com.example_company/save_game/jsonschema/1-0-2",
|
552
|
+
# {
|
553
|
+
# "saveId" => "4321",
|
554
|
+
# "level" => 23,
|
555
|
+
# "difficultyLevel" => "HARD",
|
556
|
+
# "dlContent" => true
|
557
|
+
# }
|
558
|
+
# )
|
316
559
|
#
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
560
|
+
# SnowplowTracker::Tracker.new.track_self_describing_event(event_json: self_desc_json)
|
561
|
+
#
|
562
|
+
#
|
563
|
+
# @param event_json [SelfDescribingJson] a SelfDescribingJson object
|
564
|
+
# @param context [Array<SelfDescribingJson>] an array of SelfDescribingJson objects
|
565
|
+
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
|
566
|
+
# @param subject [Subject] event-specific Subject object
|
567
|
+
# @param page [Page] event-specific Page object
|
568
|
+
#
|
569
|
+
# @api public
|
570
|
+
def track_self_describing_event(event_json:, context: nil, tstamp: nil, subject: nil, page: nil)
|
571
|
+
track_unstruct_event(event_json: event_json, context: context,
|
572
|
+
tstamp: tstamp, subject: subject, page: page)
|
573
|
+
end
|
323
574
|
|
324
|
-
|
575
|
+
# @deprecated Use {#track_self_describing_event} instead.
|
576
|
+
#
|
577
|
+
# @api public
|
578
|
+
def track_unstruct_event(event_json:, context: nil, tstamp: nil, subject: nil, page: nil)
|
579
|
+
tstamp = process_tstamp(tstamp)
|
325
580
|
|
326
|
-
|
327
|
-
|
328
|
-
end
|
581
|
+
payload = Payload.new
|
582
|
+
payload.add('e', 'ue')
|
329
583
|
|
330
|
-
|
584
|
+
envelope = SelfDescribingJson.new(UNSTRUCT_EVENT_SCHEMA, event_json.to_json)
|
585
|
+
payload.add_json(envelope.to_json, @encode_base64, 'ue_px', 'ue_pr')
|
331
586
|
|
332
|
-
|
587
|
+
finalise_payload(payload, context, tstamp, subject, page)
|
588
|
+
track(payload)
|
333
589
|
|
334
590
|
self
|
335
591
|
end
|
336
592
|
|
337
|
-
#
|
593
|
+
# Manually flush all events stored in all Tracker-associated Emitters. By
|
594
|
+
# default, this happens synchronously. {Emitter}s can only send events
|
595
|
+
# synchronously, while {AsyncEmitter}s can send either synchronously or
|
596
|
+
# asynchronously.
|
338
597
|
#
|
339
|
-
|
340
|
-
|
598
|
+
# @param async [Bool] whether to flush asynchronously or not
|
599
|
+
#
|
600
|
+
# @api public
|
601
|
+
def flush(async: false)
|
341
602
|
@emitters.each do |emitter|
|
342
603
|
emitter.flush(async)
|
343
604
|
end
|
@@ -345,27 +606,30 @@ module SnowplowTracker
|
|
345
606
|
self
|
346
607
|
end
|
347
608
|
|
348
|
-
#
|
609
|
+
# Replace the existing Tracker-associated Subject with the provided one. All
|
610
|
+
# subsequent events will have the properties of the new Subject, unless they
|
611
|
+
# are overriden by event-specific Subject parameters.
|
612
|
+
#
|
613
|
+
# @param subject [Subject] a Subject object
|
349
614
|
#
|
350
|
-
|
615
|
+
# @api public
|
351
616
|
def set_subject(subject)
|
352
617
|
@subject = subject
|
353
618
|
self
|
354
619
|
end
|
355
620
|
|
356
|
-
# Add a new
|
621
|
+
# Add a new Emitter to the internal array of Tracker-associated Emitters.
|
622
|
+
#
|
623
|
+
# @param emitter [Emitter] an Emitter object
|
357
624
|
#
|
358
|
-
|
625
|
+
# @api public
|
359
626
|
def add_emitter(emitter)
|
360
627
|
@emitters.push(emitter)
|
361
628
|
self
|
362
629
|
end
|
363
630
|
|
364
|
-
private :
|
365
|
-
:build_context,
|
631
|
+
private :build_context,
|
366
632
|
:track,
|
367
633
|
:track_ecommerce_transaction_item
|
368
|
-
|
369
634
|
end
|
370
|
-
|
371
635
|
end
|