staccato 0.1.1 → 0.2.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 +7 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +211 -3
- data/lib/staccato.rb +2 -0
- data/lib/staccato/boolean_helpers.rb +40 -0
- data/lib/staccato/exception.rb +3 -1
- data/lib/staccato/hit.rb +63 -42
- data/lib/staccato/measurable.rb +44 -0
- data/lib/staccato/measurement.rb +43 -0
- data/lib/staccato/measurement/checkout.rb +20 -0
- data/lib/staccato/measurement/checkout_option.rb +20 -0
- data/lib/staccato/measurement/impression_list.rb +26 -0
- data/lib/staccato/measurement/product.rb +34 -0
- data/lib/staccato/measurement/product_impression.rb +33 -0
- data/lib/staccato/measurement/promotion.rb +29 -0
- data/lib/staccato/measurement/transaction.rb +25 -0
- data/lib/staccato/null_measurement.rb +12 -0
- data/lib/staccato/tracker.rb +42 -11
- data/lib/staccato/version.rb +1 -1
- data/spec/integration/measurement/checkout_option_spec.rb +47 -0
- data/spec/integration/measurement/checkout_spec.rb +47 -0
- data/spec/integration/measurement/product_impression_spec.rb +61 -0
- data/spec/integration/measurement/product_spec.rb +66 -0
- data/spec/integration/measurement/promotion_spec.rb +51 -0
- data/spec/integration/measurement/transaction_spec.rb +57 -0
- data/spec/lib/staccato/measurement_spec.rb +7 -0
- metadata +48 -37
@@ -0,0 +1,44 @@
|
|
1
|
+
module Staccato
|
2
|
+
# Measurable adds field mapping and param collection
|
3
|
+
# for Measurement classes to be add to Hit
|
4
|
+
module Measurable
|
5
|
+
def self.included(model)
|
6
|
+
model.extend Forwardable
|
7
|
+
|
8
|
+
model.class_eval do
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
def_delegators :@options, *model::FIELDS.keys
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param options [Hash] options for the measurement fields
|
16
|
+
def initialize(options = {})
|
17
|
+
self.options = OptionSet.new(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# fields from options for this measurement
|
21
|
+
def fields
|
22
|
+
self.class::FIELDS
|
23
|
+
end
|
24
|
+
|
25
|
+
# measurement option prefix
|
26
|
+
# @return [String]
|
27
|
+
def prefix
|
28
|
+
''
|
29
|
+
end
|
30
|
+
|
31
|
+
# collects the parameters from options for this measurement
|
32
|
+
# @return [Hash]
|
33
|
+
def params
|
34
|
+
Hash[
|
35
|
+
fields.map { |field,key|
|
36
|
+
next if key.nil?
|
37
|
+
key = (prefix+key.to_s)
|
38
|
+
|
39
|
+
[key, options[field]] unless options[field].nil?
|
40
|
+
}.compact
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'staccato/measurable'
|
2
|
+
require 'staccato/null_measurement'
|
3
|
+
|
4
|
+
require 'staccato/measurement/checkout'
|
5
|
+
require 'staccato/measurement/checkout_option'
|
6
|
+
require 'staccato/measurement/impression_list'
|
7
|
+
require 'staccato/measurement/product'
|
8
|
+
require 'staccato/measurement/product_impression'
|
9
|
+
require 'staccato/measurement/promotion'
|
10
|
+
require 'staccato/measurement/transaction'
|
11
|
+
|
12
|
+
module Staccato
|
13
|
+
# Classes for measurements to be add to Hits
|
14
|
+
module Measurement
|
15
|
+
# List of measurement classes by lookup key
|
16
|
+
TYPES = Hash[
|
17
|
+
[
|
18
|
+
Checkout,
|
19
|
+
CheckoutOption,
|
20
|
+
ImpressionList,
|
21
|
+
Product,
|
22
|
+
ProductImpression,
|
23
|
+
Promotion,
|
24
|
+
Transaction
|
25
|
+
].map { |k| [k.lookup_key, k] }
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
# Lookup a measurement class by its key
|
29
|
+
# @param key [Symbol]
|
30
|
+
# @return [Class] measurement class or NullMeasurement
|
31
|
+
def lookup(key)
|
32
|
+
measurement_types[key] || NullMeasurement
|
33
|
+
end
|
34
|
+
module_function :lookup
|
35
|
+
|
36
|
+
# List of measurement classes by lookup key
|
37
|
+
# @return [Hash]
|
38
|
+
def measurement_types
|
39
|
+
TYPES
|
40
|
+
end
|
41
|
+
module_function :measurement_types
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for checkout action steps
|
4
|
+
class Checkout
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:checkout
|
9
|
+
end
|
10
|
+
|
11
|
+
# Checkout measurement options fields
|
12
|
+
FIELDS = {
|
13
|
+
step: 'cos', # integer
|
14
|
+
step_options: 'col' # text
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
include Measurable
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for checkout options
|
4
|
+
class CheckoutOption
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:checkout_option
|
9
|
+
end
|
10
|
+
|
11
|
+
# Checkout option measurement options fields
|
12
|
+
FIELDS = {
|
13
|
+
step: 'cos', # integer
|
14
|
+
step_options: 'col' # text
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
include Measurable
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for impressions in a list for products
|
4
|
+
class ImpressionList
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:impression_list
|
9
|
+
end
|
10
|
+
|
11
|
+
# impression list prefix
|
12
|
+
# @return [String]
|
13
|
+
def prefix
|
14
|
+
'il'+index.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Impression list measurement options fields
|
18
|
+
FIELDS = {
|
19
|
+
index: nil,
|
20
|
+
name: 'nm'
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
include Measurable
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for products in a transaction or other action
|
4
|
+
class Product
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:product
|
9
|
+
end
|
10
|
+
|
11
|
+
# product prefix
|
12
|
+
# @return [String]
|
13
|
+
def prefix
|
14
|
+
'pr'+index.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Product measurement options fields
|
18
|
+
FIELDS = {
|
19
|
+
index: nil,
|
20
|
+
id: 'id', # text
|
21
|
+
name: 'nm', # text
|
22
|
+
brand: 'br', # text
|
23
|
+
category: 'ca', # text
|
24
|
+
variant: 'va', # text
|
25
|
+
price: 'pr', # currency (looks like a double?)
|
26
|
+
quantity: 'qt', # integer
|
27
|
+
coupon_code: 'cc', # text
|
28
|
+
position: 'ps' # integer
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
include Measurable
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for product impressions in a list
|
4
|
+
class ProductImpression
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:product_impression
|
9
|
+
end
|
10
|
+
|
11
|
+
# product impress prefix
|
12
|
+
# @return [String]
|
13
|
+
def prefix
|
14
|
+
'il' + list_index.to_s + 'pr' + index.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Product impression measurement options fields
|
18
|
+
FIELDS = {
|
19
|
+
index: nil,
|
20
|
+
list_index: nil,
|
21
|
+
id: 'id', # text
|
22
|
+
name: 'nm', # text
|
23
|
+
brand: 'br', # text
|
24
|
+
category: 'ca', # text
|
25
|
+
variant: 'va', # text
|
26
|
+
price: 'pr', # currency (looks like a double?)
|
27
|
+
position: 'ps' # integer
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
include Measurable
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for promotion impressions/clicks
|
4
|
+
class Promotion
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:promotion
|
9
|
+
end
|
10
|
+
|
11
|
+
# promotion prefix
|
12
|
+
# @return [String]
|
13
|
+
def prefix
|
14
|
+
'promo'+index.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Promotion measurement options fields
|
18
|
+
FIELDS = {
|
19
|
+
index: nil,
|
20
|
+
id: 'id', # text
|
21
|
+
name: 'nm', # text
|
22
|
+
creative: 'cr', # text
|
23
|
+
position: 'ps' # text
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
include Measurable
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Staccato
|
2
|
+
module Measurement
|
3
|
+
# Measurement class for transaction (non-hit data)
|
4
|
+
class Transaction
|
5
|
+
# lookup key for use in Hit#add_measurement
|
6
|
+
# @return [Symbol]
|
7
|
+
def self.lookup_key
|
8
|
+
:transaction
|
9
|
+
end
|
10
|
+
|
11
|
+
# Transaction measurement options fields
|
12
|
+
FIELDS = {
|
13
|
+
transaction_id: 'ti',
|
14
|
+
affiliation: 'ta',
|
15
|
+
revenue: 'tr',
|
16
|
+
shipping: 'ts',
|
17
|
+
tax: 'tt',
|
18
|
+
currency: 'cu',
|
19
|
+
coupon_code: 'tcc'
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
include Measurable
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Measurement class for when we need a standin
|
2
|
+
class NullMeasurement
|
3
|
+
# Measurement fields for a noop
|
4
|
+
FIELDS = {}.freeze
|
5
|
+
|
6
|
+
# Initialize a noop measurement standin
|
7
|
+
def initialize(*); end
|
8
|
+
|
9
|
+
# Params for this noop measurement
|
10
|
+
# @return [Hash]
|
11
|
+
def params(*); {}; end
|
12
|
+
end
|
data/lib/staccato/tracker.rb
CHANGED
@@ -9,6 +9,7 @@ module Staccato
|
|
9
9
|
# sets up a new tracker
|
10
10
|
# @param id [String] the GA tracker id
|
11
11
|
# @param client_id [String, nil] unique value to track user sessions
|
12
|
+
# @param hit_defaults [Hash]
|
12
13
|
def initialize(id, client_id = nil, hit_defaults = {})
|
13
14
|
@id = id
|
14
15
|
@client_id = client_id
|
@@ -28,6 +29,17 @@ module Staccato
|
|
28
29
|
@client_id ||= Staccato.build_client_id
|
29
30
|
end
|
30
31
|
|
32
|
+
# Build a pageview
|
33
|
+
#
|
34
|
+
# @param options [Hash] options include:
|
35
|
+
# * path (optional) the path of the current page view
|
36
|
+
# * hostname (optional) the hostname of the current page view
|
37
|
+
# * title (optional) the page title
|
38
|
+
# @return [Pageview]
|
39
|
+
def build_pageview(options = {})
|
40
|
+
Staccato::Pageview.new(self, options)
|
41
|
+
end
|
42
|
+
|
31
43
|
# Track a pageview
|
32
44
|
#
|
33
45
|
# @param options [Hash] options include:
|
@@ -36,7 +48,19 @@ module Staccato
|
|
36
48
|
# * title (optional) the page title
|
37
49
|
# @return [<Net::HTTPOK] the GA `/collect` endpoint always returns a 200
|
38
50
|
def pageview(options = {})
|
39
|
-
|
51
|
+
build_pageview(options).track!
|
52
|
+
end
|
53
|
+
|
54
|
+
# Build an event
|
55
|
+
#
|
56
|
+
# @param options [Hash] options include:
|
57
|
+
# * category (optional)
|
58
|
+
# * action (optional)
|
59
|
+
# * label (optional)
|
60
|
+
# * value (optional)
|
61
|
+
# @return [Event]
|
62
|
+
def build_event(options = {})
|
63
|
+
Staccato::Event.new(self, options)
|
40
64
|
end
|
41
65
|
|
42
66
|
# Track an event
|
@@ -48,7 +72,7 @@ module Staccato
|
|
48
72
|
# * value (optional)
|
49
73
|
# @return [<Net::HTTPOK] the GA `/collect` endpoint always returns a 200
|
50
74
|
def event(options = {})
|
51
|
-
|
75
|
+
build_event(options).track!
|
52
76
|
end
|
53
77
|
|
54
78
|
# Track a social event such as a Facebook Like or Twitter Share
|
@@ -123,8 +147,10 @@ module Staccato
|
|
123
147
|
# Useful in testing
|
124
148
|
class NoopTracker
|
125
149
|
# (see Tracker#initialize)
|
126
|
-
def initialize(
|
150
|
+
def initialize(id = nil, client_id = nil, hit_defaults = {}); end
|
127
151
|
|
152
|
+
# hit defaults for our noop
|
153
|
+
# @return [Hash]
|
128
154
|
def hit_defaults
|
129
155
|
{}
|
130
156
|
end
|
@@ -139,26 +165,31 @@ module Staccato
|
|
139
165
|
nil
|
140
166
|
end
|
141
167
|
|
168
|
+
# (see Tracker#build_pageview)
|
169
|
+
def build_pageview(options = {}); end
|
142
170
|
# (see Tracker#pageview)
|
143
|
-
def pageview(
|
171
|
+
def pageview(options = {}); end
|
172
|
+
# (see Tracker#build_event)
|
173
|
+
def build_event(options = {}); end
|
144
174
|
# (see Tracker#event)
|
145
|
-
def event(
|
175
|
+
def event(options = {}); end
|
146
176
|
# (see Tracker#social)
|
147
|
-
def social(
|
177
|
+
def social(options = {}); end
|
148
178
|
# (see Tracker#exception)
|
149
|
-
def exception(
|
179
|
+
def exception(options = {}); end
|
150
180
|
# (see Tracker#timing)
|
151
|
-
def timing(
|
181
|
+
def timing(options = {}, &block)
|
152
182
|
yield if block_given?
|
153
183
|
end
|
154
184
|
# (see Tracker#transaction)
|
155
|
-
def transaction(
|
185
|
+
def transaction(options = {})
|
156
186
|
end
|
157
187
|
# (see Tracker#transaction_item)
|
158
|
-
def transaction_item(
|
188
|
+
def transaction_item(options = {})
|
159
189
|
end
|
160
190
|
|
161
|
-
|
191
|
+
# (see Tracker#track)
|
192
|
+
def track(params = {})
|
162
193
|
end
|
163
194
|
end
|
164
195
|
end
|
data/lib/staccato/version.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Staccato::Measurement::CheckoutOption do
|
4
|
+
let(:uri) {Staccato.tracking_uri}
|
5
|
+
let(:tracker) {Staccato.tracker('UA-XXXX-Y')}
|
6
|
+
let(:response) {stub(:body => '', :status => 201)}
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
SecureRandom.stubs(:uuid).returns('555')
|
10
|
+
Net::HTTP.stubs(:post_form).returns(response)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'a pageview with a transaction' do
|
14
|
+
let(:pageview) {
|
15
|
+
tracker.build_pageview({
|
16
|
+
path: '/checkout', hostname: 'mystore.com',
|
17
|
+
title: 'Complete Your Checkout', product_action: 'checkout_option'
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
let(:measurment_options) {{
|
22
|
+
step: 1,
|
23
|
+
step_options: 'Visa'
|
24
|
+
}}
|
25
|
+
|
26
|
+
before(:each) do
|
27
|
+
pageview.add_measurement(:checkout_option, measurment_options)
|
28
|
+
|
29
|
+
pageview.track!
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'tracks the measurment' do
|
33
|
+
Net::HTTP.should have_received(:post_form).with(uri, {
|
34
|
+
'v' => 1,
|
35
|
+
'tid' => 'UA-XXXX-Y',
|
36
|
+
'cid' => '555',
|
37
|
+
't' => 'pageview',
|
38
|
+
'dh' => 'mystore.com',
|
39
|
+
'dp' => '/checkout',
|
40
|
+
'dt' => 'Complete Your Checkout',
|
41
|
+
'pa' => 'checkout_option',
|
42
|
+
'cos' => 1,
|
43
|
+
'col' => 'Visa'
|
44
|
+
})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|