Staccato

Ruby library to track into the official Google Analytics Measurement Protocol

https://developers.google.com/analytics/devguides/collection/protocol/v1/

NOTE: The Measurement Protocol is part of Universal Analytics, which is currently available in public beta. Data from the measurement protocol will only be processed in Universal Analytics enabled properties.

Gem Version Build Status Code Climate

Installation

Add this line to your application's Gemfile:

gem 'staccato'

And then execute:

$ bundle

Or install it yourself as:

$ gem install staccato

Usage

tracker = Staccato.tracker('UA-XXXX-Y') # REQUIRED, your Google Analytics Tracking ID

#tracker optionally takes a second param for the client_id value. By default, the client_id is set to a random UUID with SecureRandom.uuid

Track some data

# Track a Pageview (all values optional)
tracker.pageview(path: '/page-path', hostname: 'mysite.com', title: 'A Page!')

# Track an Event (all values optional)
tracker.event(category: 'video', action: 'play', label: 'cars', value: 1)

# Track social activity (all values REQUIRED)
tracker.social(action: 'like', network: 'facebook', target: '/something')

# Track exceptions (all values optional)
tracker.exception(description: 'RuntimeException', fatal: true)

# Track timing (all values optional, but should include time)
tracker.timing(category: 'runtime', variable: 'db', label: 'query', time: 50) # time in milliseconds

tracker.timing(category: 'runtime', variable: 'db', label: 'query') do
  some_code_here
end

# Track transaction (transaction_id REQUIRED)
tracker.transaction({
  transaction_id: 12345,
  affiliation: 'clothing',
  revenue: 17.98,
  shipping: 2.00,
  tax: 2.50,
  currency: 'EUR'
})

# Track transaction item (matching transaction_id and item name REQUIRED)
tracker.transaction_item({
  transaction_id: 12345,
  name: 'Shirt',
  price: 8.99,
  quantity: 2,
  code: 'afhcka1230',
  variation: 'red',
  currency: 'EUR'
})

"Global" Options

Any of the options on the parameters list (https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters) that are accepted on ALL hit types can be set as options on any of the hits.

tracker.pageview(path: '/video/1235', flash_version: 'v1.2.3')

Flash Version is a global option in the example above.

Note: There are a few options that if used will override global options:

  • document_path: overriden by path in pageviews
  • document_hostname: overriden by hostname in pageviews
  • document_title: overriden by title in pageviews

These are holdovers from the original design, where pageview is a hit type that can take any/all of the optional parameters. path, hostname, and title are slightly nicer to use on pageview.

The complete list at this time:

Staccato::Hit::GLOBAL_OPTIONS.keys # =>

[:anonymize_ip,
 :queue_time,
 :cache_buster,
 :user_id,
 :user_ip,
 :user_agent,
 :referrer,
 :campaign_name,
 :campaign_source,
 :campaign_medium,
 :campaign_keyword,
 :campaign_content,
 :campaign_id,
 :adwords_id,
 :display_ads_id,
 :screen_resolution,
 :viewport_size,
 :screen_colors,
 :user_language,
 :java_enabled,
 :flash_version,
 :document_location,
 :document_encoding,
 :document_hostname,
 :document_path,
 :document_title,
 :link_id,
 :application_name,
 :application_version,
 :application_id,
 :application_installer_id,
 :experiment_id,
 :experiment_variant,
 :product_action,
 :product_action_list,
 :promotion_action,
 :geographical_id]

Boolean options like anonymize_ip will be converted from true/false into 1/0 as per the tracking API docs.

Custom Dimensions and Metrics

hit = Staccato::Pageview.new(tracker, hostname: 'mysite.com', path: '/sports-page-5', title: 'Sports Page #5')
hit.add_custom_dimension(19, 'Sports')
hit.add_custom_metric(2, 5)
hit.track!

The first argument is the slot position. Custom dimensions and metrics have 20 slots or 200 if you're "Premium" account.

The second argument is the value. For dimensions, that's a text value. For metrics it is an integer.

Non-Interactive Hit

# Track a Non-Interactive Hit
tracker.event(category: 'video', action: 'play', non_interactive: true)

Non-Interactive events are useful for tracking things like emails sent, or other events that are not directly the result of a user's interaction.

The option non_interactive is accepted for all methods on tracker.

Session Control

# start a session
tracker.pageview(path: '/blog', start_session: true)

# end a session
tracker.pageview(path: '/blog', end_session: true)

Other options are acceptable to start and end a session: session_start, session_end, and stop_session.

Content Experiment

# Tracking an Experiment
#   useful for tracking A/B or Multivariate testing
tracker.pageview({
  path: '/blog',
  experiment_id: 'a7a8d91df',
  experiment_variant: 'a'
})

Tracker Hit Defaults

Global parameters can be set as defaults on the tracker, and will be used for all hits (unless overwritten by parameters set directly on a hit).

The following example creates a tracker with a default hostname. The two pageviews will track the default hostname and the page path passed in.

tracker = Staccato.tracker('UA-XXXX-Y', client_id, {document_hostname: 'example.com'})

tracker.pageview(path: '/videos/123')
tracker.pageview(path: '/videos/987')

Additional Measurements

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 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.

Note: Certain Measurements require an index. This is an integer (usually) between 1 and 200 inclusive.

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:

  • detail
  • click
  • add
  • remove
  • checkout
  • checkout_option
  • purchase
  • refund

Transaction w/ Product

Using a pageview to track a transaction with a product (using the 'purchase' as the product_action:

pageview = tracker.build_pageview(path: '/receipt', hostname: 'mystore.com', title: 'Your Receipt', product_action: 'purchase')

pageview.add_measurement(:transaction, {
  transaction_id: 'T12345',
  affiliation: 'Your Store',
  revenue: 37.39,
  tax: 2.85,
  shipping: 5.34,
  currency: 'USD',
  coupon_code: 'SUMMERSALE'
})

pageview.add_measurement(:product, {
  index: 1, # this is our first product, value may be 1-200
  id: 'P12345',
  name: 'T-Shirt',
  category: 'Apparel',
  brand: 'Your Brand',
  variant: 'Purple',
  quantity: 2,
  position: 1,
  price: 14.60,
  coupon_code: 'ILUVTEES'
})

pageview.track!

Transaction Refund

The combination of product_action: 'refund' and transaction measurement setting a matching id to a previous transaction.

event = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')

event.add_measurement(:transaction, id: 'T12345')

event.track!

Transaction & Product Refund

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.

event = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')

event.add_measurement(:transaction, id: 'T12345')
event.add_measurement(:product, index: 1, id: 'P12345', quantity: 1)

event.track!

Promotion Impression

pageview = tracker.build_pageview(path: '/search', hostname: 'mystore.com', title: 'Search Results')

pageview.add_measurement(:promotion, {
  index: 1,
  id: 'PROMO_1234',
  name: 'Summer Sale',
  creative: 'summer_sale_banner',
  position: 'banner_1'
})

pageview.track!

Promotion Click

Promotion also supports a promotion_action, similar to product_action. This is another global option on Hit.

event = tracker.build_event(category: 'promotions', action: 'click', label: 'internal', promotion_action: 'click')

event.add_measurement(:promotion, {
  index: 1,
  id: 'PROMO_1234',
  name: 'Summer Sale',
  creative: 'summer_sale_banner',
  position: 'banner_1'
})

event.track!

Product Click

event = tracker.build_event(category: 'search', action: 'click', label: 'results', product_action: 'click', product_action_list: 'Search Results')

event.add_measurement(:product, {
  index: 1,
  id: 'P12345',
  name: 'T-Shirt',
  category: 'Apparel',
  brand: 'Your Brand',
  variant: 'Purple',
  quantity: 2,
  position: 1,
  price: 14.60,
  coupon_code: 'ILUVTEES'
})

event.track!

Checkout

pageview = tracker.build_pageview(path: '/checkout', hostname: 'mystore.com', title: 'Complete Your Checkout', product_action: 'checkout')

pageview.add_measurement(:product, {
  index: 1, # this is our first product, value may be 1-200
  id: 'P12345',
  name: 'T-Shirt',
  category: 'Apparel',
  brand: 'Your Brand',
  variant: 'Purple',
  quantity: 2,
  position: 1,
  price: 14.60,
  coupon_code: 'ILUVTEES'
})

pageview.add_measurement(:checkout, {
  step: 1,
  step_option: 'Visa'
})

pageview.track!

Checkout Option

event = tracker.build_event(category: 'checkout', action: 'option', non_interactive: true, product_action: 'checkout_option')

event.add_measurement(:checkout_options, {
  step: 2,
  step_option: 'Fedex'
})

event.track!

Impression List & Product Impression

pageview = tracker.build_pageview(path: '/home', hostname: 'mystore.com', title: 'Home Page')

pageview.add_measurement(:impression_list, index: 1, name: 'Search Results')

pageview.add_measurement(:product_impression, {
  index: 1,
  list_index: 1, # match the impression_list above
  id: 'P12345',
  name: 'T-Shirt',
  category: 'Apparel',
  brand: 'Your Brand',
  variant: 'Purple',
  position: 1,
  price: 14.60
})

pageview.add_measurement(:impression_list, index: 2, name: 'Recommendations')

pageview.add_measurement(:product_impression, {
  index: 1,
  list_index: 2,
  name: 'Yellow Tee'
})

pageview.add_measurement(:product_impression, {
  index: 2,
  list_index: 2,
  name: 'Red Tee'
})

pageview.track!

Screenview (as in mobile)

tracker.screenview({
  screen_name: 'user1'
})

## Google Documentation ##

https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide
https://developers.google.com/analytics/devguides/collection/protocol/v1/reference
https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters

## HTTP Adapters ##

Staccato provides a number of basic adapters to different ruby http libraries. By default, Staccato uses `net/http` when you create a new tracker. If you are using Faraday or [The Ruby HTTP library](https://github.com/httprb/http.rb) Staccato provides adapters.

```ruby
tracker = Staccato.tracker('UA-XXXX-Y') do |c|
  c.adapter = Staccato::Adapter::Faraday.new(Stacatto.ga_collection_uri) do |faraday|
    # further faraday configuration here
  end
end

You can also make your own Adapters by implementing any class that responds to post with a hash of params/data to be posted. The default adapters all accept the URI in the initializer, but this is not a requirement for yours.

One such example might be for a new net/http adapter which accepts more options for configuring the connection:

class CustomAdapter
  attr_reader :uri

  def initialize(uri, options={})
    @uri = uri
    @options = options
  end

  def post(data)
    Net::HTTP::Post.new(uri.request_uri).tap do |request|
      request.read_timeout = @options.fetch(:read_timeout, 90)
      request.form_data = data

      execute(request)
    end
  end

  private
  def execute(request)
    Net::HTTP.new(uri.hostname, uri.port).start do |http|
      http.open_timeout = @options.fetch(:open_timeout, 90)
      http.request(request)
    end
  end
end

Which would be used like:

tracker = Staccato.tracker('UA-XXXX-Y') do |c|
  c.adapter = CustomAdapter.new(Stacatto.ga_collection_uri, read_timeout: 1, open_time: 1)
end

Validation Adapter

The validation adapter sends hits to the debug endpoint, which responds with information about the validity of the hit.

tracker = Staccato.tracker('UA-XXXX-Y') do |c|
  c.adapter = Staccato::Adapter::Validate.new
end

By default, the staccato default_adapter is used to send validation hits, but a different adapter can be used (e.g. Faraday or Net::HTTP).

tracker = Staccato.tracker('UA-XXXX-Y') do |c|
  c.adapter = Staccato::Adapter::Validate.new(Staccato::Adapter::HTTP)
end

UDP Adapter for Staccato::Proxy

If you're using Staccato::Proxy, you can point Staccato at it using the UDP adapter.

tracker = Staccato.tracker('UA-XXXX-Y') do |c|
  c.adapter = Staccato::Adapter::UDP.new(URI('udp://127.0.0.1:3003'))
end

Be sure to set the ip or host and port to wherever you have configured Staccato::Proxy to run.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request