staccato 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9a674a0fa88901074b28c03b98647b7751322412
4
+ data.tar.gz: d6db8298721e606f0855c54959eb42537e0d496c
5
+ SHA512:
6
+ metadata.gz: 323fdd8c54466b98790b5f446c1d20464bb3ce8271a2785881d49d4edf3257602367e05cdfdc38db84d21d5fc5164684c1b25f87d422ee35933c5da7a840b0d5
7
+ data.tar.gz: 5b290b2d0602444e10562619feef4763161e546aea5dedad33ad0c3eb680633a22566cc37da718674a2587515fd4921e6b86c044a02c61f4a5fb9d273feaef3b
@@ -4,4 +4,5 @@ rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
6
  - 2.1.0
7
+ - 2.2.0
7
8
  script: bundle exec rake
@@ -1,3 +1,11 @@
1
+ ## Staccato 0.2.0 ##
2
+
3
+ * Enhanced Ecommerce Measurements
4
+ * Measurable module for further extension of hits
5
+ * New global hit options for ecommerce
6
+
7
+ *Tony Pitale <@tpitale>*
8
+
1
9
  ## Staccato 0.1.1 ##
2
10
 
3
11
  * fixes NoopTracker when track! is called on a hit *martin1keogh*
data/README.md CHANGED
@@ -133,7 +133,10 @@ Staccato::Hit::GLOBAL_OPTIONS.keys # =>
133
133
  :application_id,
134
134
  :application_installer_id,
135
135
  :experiment_id,
136
- :experiment_variant]
136
+ :experiment_variant,
137
+ :product_action,
138
+ :product_action_list,
139
+ :promotion_action]
137
140
  ```
138
141
 
139
142
  Boolean options like `anonymize_ip` will be converted from `true`/`false` into `1`/`0` as per the tracking API docs.
@@ -200,13 +203,218 @@ tracker.pageview(path: '/videos/123')
200
203
  tracker.pageview(path: '/videos/987')
201
204
  ```
202
205
 
203
- ## Google Documentation
206
+ ## Additional Measurements ##
207
+
208
+ Additional Measurements can be added to any Hit type, but most commonly used with pageviews or events. The current set of measurements is for handling [Enhanced Ecommerce](https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#enhancedecom) measurements. I've grouped these into ImpressionList, Product, ProductImpression, Promotion, Transaction, Checkout, and CheckoutOption (w/ ImpressionList). Each can be added and combined – per Google's documentation – onto an existing Hit.
209
+
210
+ **Note:** Certain Measurements require an `index`. This is an integer (usually) between 1 and 200 inclusive.
211
+
212
+ **Note:** Certain Measurements require a `product_action` to be set. This is a global option, and should be set on the original hit. The `product_action` can be any one of:
213
+
214
+ * `detail`
215
+ * `click`
216
+ * `add`
217
+ * `remove`
218
+ * `checkout`
219
+ * `checkout_option`
220
+ * `purchase`
221
+ * `refund`
222
+
223
+ ### Transaction w/ Product ###
224
+
225
+ Using a pageview to track a transaction with a product (using the 'purchase' as the `product_action`:
226
+
227
+ ```ruby
228
+ pageview = tracker.build_pageview(path: '/receipt', hostname: 'mystore.com', title: 'Your Receipt', product_action: 'purchase')
229
+
230
+ pageview.add_measurement(:transaction, {
231
+ transaction_id: 'T12345',
232
+ affiliation: 'Your Store',
233
+ revenue: 37.39,
234
+ tax: 2.85,
235
+ shipping: 5.34,
236
+ currency: 'USD',
237
+ coupon_code: 'SUMMERSALE'
238
+ })
239
+
240
+ pageview.add_measurement(:product, {
241
+ index: 1, # this is our first product, value may be 1-200
242
+ id: 'P12345',
243
+ name: 'T-Shirt',
244
+ category: 'Apparel',
245
+ brand: 'Your Brand',
246
+ variant: 'Purple',
247
+ quantity: 2,
248
+ position: 1,
249
+ price: 14.60,
250
+ coupon_code: 'ILUVTEES'
251
+ })
252
+
253
+ pageview.track!
254
+ ```
255
+
256
+ ### Transaction Refund ###
257
+
258
+ The combination of `product_action: 'refund'` and `transaction` measurement setting a matching `id` to a previous transaction.
259
+
260
+ ```ruby
261
+ event = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')
262
+
263
+ event.add_measurement(:transaction, id: 'T12345')
264
+
265
+ event.track!
266
+ ```
267
+
268
+ ### Transaction & Product Refund ###
269
+
270
+ The combination of `product_action: 'refund'` and `transaction` measurement setting a matching `id` to a previous transaction. You can also specify a product (or products, using `index`) with a `quantity` (for partial refunds) to refund.
271
+
272
+ ```ruby
273
+ event = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')
274
+
275
+ event.add_measurement(:transaction, id: 'T12345')
276
+ event.add_measurement(:product, index: 1, id: 'P12345', quantity: 1)
277
+
278
+ event.track!
279
+ ```
280
+
281
+ ### Promotion Impression ###
282
+
283
+ ```ruby
284
+ pageview = tracker.build_pageview(path: '/search', hostname: 'mystore.com', title: 'Search Results')
285
+
286
+ pageview.add_measurement(:promotion, {
287
+ index: 1,
288
+ id: 'PROMO_1234',
289
+ name: 'Summer Sale',
290
+ creative: 'summer_sale_banner',
291
+ position: 'banner_1'
292
+ })
293
+
294
+ pageview.track!
295
+ ```
296
+
297
+ ### Promotion Click ###
298
+
299
+ Promotion also supports a `promotion_action`, similar to `product_action`. This is another global option on `Hit`.
300
+
301
+ ```ruby
302
+ event = tracker.build_event(category: 'promotions', action: 'click', label: 'internal', promotion_action: 'click')
303
+
304
+ event.add_measurement(:promotion, {
305
+ index: 1,
306
+ id: 'PROMO_1234',
307
+ name: 'Summer Sale',
308
+ creative: 'summer_sale_banner',
309
+ position: 'banner_1'
310
+ })
311
+
312
+ event.track!
313
+ ```
314
+
315
+ ### Product Click ###
316
+
317
+ ```ruby
318
+ event = tracker.build_event(category: 'search', action: 'click', label: 'results', product_action: 'click', product_action_list: 'Search Results')
319
+
320
+ event.add_measurement(:product, {
321
+ index: 1,
322
+ id: 'P12345',
323
+ name: 'T-Shirt',
324
+ category: 'Apparel',
325
+ brand: 'Your Brand',
326
+ variant: 'Purple',
327
+ quantity: 2,
328
+ position: 1,
329
+ price: 14.60,
330
+ coupon_code: 'ILUVTEES'
331
+ })
332
+
333
+ event.track!
334
+ ```
335
+
336
+ ### Checkout ###
337
+
338
+ ```ruby
339
+ pageview = tracker.build_pageview(path: '/checkout', hostname: 'mystore.com', title: 'Complete Your Checkout', product_action: 'checkout')
340
+
341
+ pageview.add_measurement(:product, {
342
+ index: 1, # this is our first product, value may be 1-200
343
+ id: 'P12345',
344
+ name: 'T-Shirt',
345
+ category: 'Apparel',
346
+ brand: 'Your Brand',
347
+ variant: 'Purple',
348
+ quantity: 2,
349
+ position: 1,
350
+ price: 14.60,
351
+ coupon_code: 'ILUVTEES'
352
+ })
353
+
354
+ pageview.add_measurement(:checkout, {
355
+ step: 1,
356
+ step_option: 'Visa'
357
+ })
358
+
359
+ pageview.track!
360
+ ```
361
+
362
+ ### Checkout Option ###
363
+
364
+ ```ruby
365
+ event = tracker.build_event(category: 'checkout', action: 'option', non_interactive: true, product_action: 'checkout_option')
366
+
367
+ event.add_measurement(:checkout_options, {
368
+ step: 2,
369
+ step_option: 'Fedex'
370
+ })
371
+
372
+ event.track!
373
+ ```
374
+
375
+ ### Impression List & Product Impression ###
376
+
377
+ ```ruby
378
+ pageview = tracker.build_pageview(path: '/home', hostname: 'mystore.com', title: 'Home Page')
379
+
380
+ pageview.add_measurement(:impression_list, index: 1, name: 'Search Results')
381
+
382
+ pageview.add_measurement(:product_impression, {
383
+ index: 1,
384
+ list_index: 1, # match the impression_list above
385
+ id: 'P12345',
386
+ name: 'T-Shirt',
387
+ category: 'Apparel',
388
+ brand: 'Your Brand',
389
+ variant: 'Purple',
390
+ position: 1,
391
+ price: 14.60
392
+ })
393
+
394
+ pageview.add_measurement(:impression_list, index: 2, name: 'Recommendations')
395
+
396
+ pageview.add_measurement(:product_impression, {
397
+ index: 1,
398
+ list_index: 2,
399
+ name: 'Yellow Tee'
400
+ })
401
+
402
+ pageview.add_measurement(:product_impression, {
403
+ index: 2,
404
+ list_index: 2,
405
+ name: 'Red Tee'
406
+ })
407
+
408
+ pageview.track!
409
+ ```
410
+
411
+ ## Google Documentation ##
204
412
 
205
413
  https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide
206
414
  https://developers.google.com/analytics/devguides/collection/protocol/v1/reference
207
415
  https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
208
416
 
209
- ## Contributing
417
+ ## Contributing ##
210
418
 
211
419
  1. Fork it
212
420
  2. Create your feature branch (`git checkout -b my-new-feature`)
@@ -38,6 +38,7 @@ module Staccato
38
38
  end
39
39
  end
40
40
 
41
+ require 'staccato/boolean_helpers'
41
42
  require 'staccato/option_set'
42
43
  require 'staccato/hit'
43
44
  require 'staccato/pageview'
@@ -48,3 +49,4 @@ require 'staccato/timing'
48
49
  require 'staccato/transaction'
49
50
  require 'staccato/transaction_item'
50
51
  require 'staccato/tracker'
52
+ require 'staccato/measurement'
@@ -0,0 +1,40 @@
1
+ # Collection of methods for converting to
2
+ # google's boolean integers from ruby's booleans
3
+ module BooleanHelpers
4
+ # Convert each boolean in the hash to integer
5
+ # if it is a boolean field
6
+ # @param hash [Hash]
7
+ # @return [Hash]
8
+ def convert_booleans(hash)
9
+ hash.each_pair.with_object({}, &method(:convert_boolean))
10
+ end
11
+
12
+ # Method to convert a single field from bool to int
13
+ # @param hash [#[]=] the collector object
14
+ def convert_boolean((k,v), hash)
15
+ hash[k] = boolean_field?(k) ? integer_for(v) : v
16
+ end
17
+
18
+ # Is this key one of the boolean fields
19
+ # @param key [Symbol] field key
20
+ # @return [Boolean]
21
+ def boolean_field?(key)
22
+ boolean_fields.include?(key)
23
+ end
24
+
25
+ # Convert a value to appropriate int
26
+ # @param value [nil, true, false, Integer]
27
+ # @return [nil, Integer]
28
+ def integer_for(value)
29
+ case value
30
+ when Integer
31
+ value
32
+ when TrueClass
33
+ 1
34
+ when FalseClass
35
+ 0
36
+ when NilClass
37
+ nil
38
+ end
39
+ end
40
+ end
@@ -15,8 +15,10 @@ module Staccato
15
15
  :exception
16
16
  end
17
17
 
18
+ # Boolean fields from Hit plus exception-specific field
19
+ # @return [Array<Symbol>]
18
20
  def boolean_fields
19
- super << :fatal
21
+ super + [:fatal]
20
22
  end
21
23
  end
22
24
  end
@@ -18,6 +18,7 @@ module Staccato
18
18
  end
19
19
  end
20
20
 
21
+ # Hit global options may be set on any hit type options
21
22
  GLOBAL_OPTIONS = {
22
23
  anonymize_ip: 'aip', # boolean
23
24
  queue_time: 'qt', # integer
@@ -63,14 +64,22 @@ module Staccato
63
64
 
64
65
  # Content Experiments
65
66
  experiment_id: 'xid',
66
- experiment_variant: 'xvar'
67
- }
67
+ experiment_variant: 'xvar',
68
68
 
69
+ # Product
70
+ product_action: 'pa',
71
+ product_action_list: 'pal',
72
+
73
+ # Promotion
74
+ promotion_action: 'promoa'
75
+ }.freeze
76
+
77
+ # Fields which should be converted to boolean for google
69
78
  BOOLEAN_FIELDS = [
70
79
  :non_interactive,
71
80
  :anonymize_ip,
72
81
  :java_enabled
73
- ]
82
+ ].freeze
74
83
 
75
84
  # sets up a new hit
76
85
  # @param tracker [Staccato::Tracker] the tracker to collect to
@@ -88,31 +97,60 @@ module Staccato
88
97
 
89
98
  # collects the parameters from options for this hit type
90
99
  def params
91
- base_params.
92
- merge(tracker_default_params).
93
- merge(global_options_params).
94
- merge(hit_params).
95
- merge(custom_dimensions).
96
- merge(custom_metrics).
97
- reject {|_,v| v.nil?}
98
- end
99
-
100
- def add_custom_dimension(position, value)
101
- self.custom_dimensions["cd#{position}"] = value
102
- end
103
-
100
+ {}.
101
+ merge!(base_params).
102
+ merge!(tracker_default_params).
103
+ merge!(global_options_params).
104
+ merge!(hit_params).
105
+ merge!(custom_dimensions).
106
+ merge!(custom_metrics).
107
+ merge!(measurement_params).
108
+ reject {|_,v| v.nil?}
109
+ end
110
+
111
+ # Set a custom dimension value at an index
112
+ # @param index [Integer]
113
+ # @param value
114
+ def add_custom_dimension(index, value)
115
+ self.custom_dimensions["cd#{index}"] = value
116
+ end
117
+
118
+ # Custom dimensions for this hit
119
+ # @return [Hash]
104
120
  def custom_dimensions
105
121
  @custom_dimensions ||= {}
106
122
  end
107
123
 
108
- def add_custom_metric(position, value)
109
- self.custom_metrics["cm#{position}"] = value
124
+ # Set a custom metric value at an index
125
+ # @param index [Integer]
126
+ # @param value
127
+ def add_custom_metric(index, value)
128
+ self.custom_metrics["cm#{index}"] = value
110
129
  end
111
130
 
131
+ # Custom metrics for this hit
132
+ # @return [Hash]
112
133
  def custom_metrics
113
134
  @custom_metrics ||= {}
114
135
  end
115
136
 
137
+ # Add a measurement by its symbol name with options
138
+ #
139
+ # @param key [Symbol] any one of the measurable classes lookup key
140
+ # @param options [Hash] options for the measurement
141
+ def add_measurement(key, options = {})
142
+ self.measurements << Measurement.lookup(key).new(options)
143
+ end
144
+
145
+ # Measurements for this hit
146
+ # @return [Array<Measurable>]
147
+ def measurements
148
+ @measurements ||= []
149
+ end
150
+
151
+ # Returns the value for session control
152
+ # based on options for session_start/_end
153
+ # @return ['start', 'end']
116
154
  def session_control
117
155
  case
118
156
  when options[:session_start], options[:start_session]
@@ -128,34 +166,12 @@ module Staccato
128
166
  end
129
167
 
130
168
  private
131
- def convert_booleans(hash)
132
- hash.each_pair.with_object({}, &method(:convert_boolean))
133
- end
134
-
135
- def convert_boolean((k,v), hash)
136
- hash[k] = boolean_field?(k) ? integer_for(v) : v
137
- end
138
-
169
+ # @private
139
170
  def boolean_fields
140
171
  BOOLEAN_FIELDS
141
172
  end
142
173
 
143
- def boolean_field?(key)
144
- boolean_fields.include?(key)
145
- end
146
-
147
- def integer_for(value)
148
- case value
149
- when Integer
150
- value
151
- when TrueClass
152
- 1
153
- when FalseClass
154
- 0
155
- when NilClass
156
- nil
157
- end
158
- end
174
+ include BooleanHelpers
159
175
 
160
176
  # @private
161
177
  def base_params
@@ -199,5 +215,10 @@ module Staccato
199
215
  }.compact
200
216
  ]
201
217
  end
218
+
219
+ # @private
220
+ def measurement_params
221
+ measurements.dup.map!(&:params).inject({}, &:merge!)
222
+ end
202
223
  end
203
224
  end