{"id":13482745,"url":"https://github.com/tpitale/staccato","last_synced_at":"2025-05-15T15:02:28.313Z","repository":{"id":9865998,"uuid":"11864512","full_name":"tpitale/staccato","owner":"tpitale","description":"Ruby library to perform server-side tracking into the official Google Analytics Measurement Protocol","archived":false,"fork":false,"pushed_at":"2023-06-01T14:35:06.000Z","size":196,"stargazers_count":388,"open_issues_count":2,"forks_count":43,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-31T16:13:09.515Z","etag":null,"topics":["analytics","analytics-api","analytics-tracking","google-analytics","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tpitale.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2013-08-03T14:47:50.000Z","updated_at":"2025-02-23T22:14:26.000Z","dependencies_parsed_at":"2022-08-26T23:02:22.818Z","dependency_job_id":"94aaea49-4f32-4495-ac2e-03cb29e8c535","html_url":"https://github.com/tpitale/staccato","commit_stats":{"total_commits":135,"total_committers":17,"mean_commits":"7.9411764705882355","dds":"0.19999999999999996","last_synced_commit":"a64f41ae3f05f8949f87689b977ebaa8cf1d12d3"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpitale%2Fstaccato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpitale%2Fstaccato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpitale%2Fstaccato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpitale%2Fstaccato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tpitale","download_url":"https://codeload.github.com/tpitale/staccato/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247694875,"owners_count":20980733,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","analytics-api","analytics-tracking","google-analytics","ruby"],"created_at":"2024-07-31T17:01:05.100Z","updated_at":"2025-04-07T17:06:42.279Z","avatar_url":"https://github.com/tpitale.png","language":"Ruby","funding_links":[],"categories":["Analytics"],"sub_categories":[],"readme":"# Staccato\n\nRuby library to track into the official Google Analytics Measurement Protocol\n\nhttps://developers.google.com/analytics/devguides/collection/protocol/v1/\n\n**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.\n\n[![Gem Version](https://badge.fury.io/rb/staccato.png)](http://badge.fury.io/rb/staccato)\n[![Build Status](https://travis-ci.org/tpitale/staccato.png?branch=master)](https://travis-ci.org/tpitale/staccato)\n[![Code Climate](https://codeclimate.com/github/tpitale/staccato.png)](https://codeclimate.com/github/tpitale/staccato)\n\nIf you're using Rails and would like to use Staccato, we have an gem for that! [Staccato Rails](https://github.com/tpitale/staccato-rails)\n\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'staccato'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install staccato\n\n## Usage ##\n\n```ruby\ntracker = Staccato.tracker('UA-XXXX-Y') # REQUIRED, your Google Analytics Tracking ID\n```\n\n`#tracker` optionally takes a second param for the `client_id` value.\nBy default, the `client_id` is set to a random UUID with `SecureRandom.uuid`\n\n### Setting SSL on a tracker ###\n\n```ruby\n# passing nil as the second argument lets Staccato build the client id, as the default\ntracker = Staccato.tracker('UA-XXXX-Y', nil, ssl: true)\n```\n\n### Track some data ###\n\n```ruby\n# Track a Pageview (all values optional)\ntracker.pageview(path: '/page-path', hostname: 'mysite.com', title: 'A Page!')\n\n# Track an Event (all values optional)\ntracker.event(category: 'video', action: 'play', label: 'cars', value: 1)\n\n# Track social activity (all values REQUIRED)\ntracker.social(action: 'like', network: 'facebook', target: '/something')\n\n# Track exceptions (all values optional)\ntracker.exception(description: 'RuntimeException', fatal: true)\n\n# Track timing (all values optional, but should include time)\ntracker.timing(category: 'runtime', variable: 'db', label: 'query', time: 50) # time in milliseconds\n\ntracker.timing(category: 'runtime', variable: 'db', label: 'query') do\n  some_code_here\nend\n\n# Track transaction (transaction_id REQUIRED)\ntracker.transaction({\n  transaction_id: 12345,\n  affiliation: 'clothing',\n  revenue: 17.98,\n  shipping: 2.00,\n  tax: 2.50,\n  currency: 'EUR'\n})\n\n# Track transaction item (matching transaction_id and item name REQUIRED)\ntracker.transaction_item({\n  transaction_id: 12345,\n  name: 'Shirt',\n  price: 8.99,\n  quantity: 2,\n  code: 'afhcka1230',\n  variation: 'red',\n  currency: 'EUR'\n})\n```\n\n### Building Hits ###\n\nIf you need access to a hit, you can use `tracker.build_\u003chit type\u003e` and pass it the same options as the above tracker methods. For example, these are all the same:\n\n```ruby\n# build and track a Staccato::Pageview in a single step\ntracker.pageview(options_hash)\n\n# build, and then track, a pageview\ntracker.build_pageview(options_hash).track!\n\n# build a Staccato::Pageview, then track it\nhit = Staccato::Pageview.new(tracker, options_hash)\nhit.track!\n```\n\n### \"Global\" Options ###\n\nAny 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.\n\n```ruby\ntracker.pageview(path: '/video/1235', flash_version: 'v1.2.3')\n```\n\nFlash Version is a global option in the example above.\n\n**Note:** There are a few options that if used will override global options:\n\n* document_path: overriden by `path` in pageviews\n* document_hostname: overriden by `hostname` in pageviews\n* document_title: overriden by `title` in pageviews\n\nThese 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`.\n\nThe complete list at this time:\n\n```ruby\nStaccato::Hit::GLOBAL_OPTIONS.keys # =\u003e\n\n[:anonymize_ip,\n :queue_time,\n :data_source,\n :cache_buster,\n :user_id,\n :user_ip,\n :user_agent,\n :referrer,\n :campaign_name,\n :campaign_source,\n :campaign_medium,\n :campaign_keyword,\n :campaign_content,\n :campaign_id,\n :adwords_id,\n :display_ads_id,\n :screen_resolution,\n :viewport_size,\n :screen_colors,\n :user_language,\n :java_enabled,\n :flash_version,\n :document_location,\n :document_encoding,\n :document_hostname,\n :document_path,\n :document_title,\n :link_id,\n :application_name,\n :application_version,\n :application_id,\n :application_installer_id,\n :experiment_id,\n :experiment_variant,\n :product_action,\n :product_action_list,\n :promotion_action,\n :geographical_id]\n```\n\nBoolean options like `anonymize_ip` will be converted from `true`/`false` into `1`/`0` as per the tracking API docs.\n\nThe `data_source` option can take any value, but note that hits sent from other Google tools will have specific values.  Hits sent from analytics.js will have `data_source` set to `web`, and hits sent from one of the mobile SDKs will have `data_source` set to `app`.\n\n#### Custom Dimensions and Metrics ####\n\n```ruby\nhit = Staccato::Pageview.new(tracker, hostname: 'mysite.com', path: '/sports-page-5', title: 'Sports Page #5')\nhit.add_custom_dimension(19, 'Sports')\nhit.add_custom_metric(2, 5)\nhit.track!\n```\n\nThe first argument is the slot position. Custom dimensions and metrics have 20 slots or 200 if you're \"Premium\" account.\n\nThe second argument is the value. For dimensions, that's a text value. For metrics it is an integer.\n\n#### Non-Interactive Hit ####\n\n```ruby\n# Track a Non-Interactive Hit\ntracker.event(category: 'video', action: 'play', non_interactive: true)\n```\n\nNon-Interactive events are useful for tracking things like emails sent, or other\nevents that are not directly the result of a user's interaction.\n\nThe option `non_interactive` is accepted for all methods on `tracker`.\n\n#### Session Control ####\n\n```ruby\n# start a session\ntracker.pageview(path: '/blog', start_session: true)\n\n# end a session\ntracker.pageview(path: '/blog', end_session: true)\n```\n\nOther options are acceptable to start and end a session: `session_start`, `session_end`, and `stop_session`.\n\n#### Content Experiment ####\n\n```ruby\n# Tracking an Experiment\n#   useful for tracking A/B or Multivariate testing\ntracker.pageview({\n  path: '/blog',\n  experiment_id: 'a7a8d91df',\n  experiment_variant: 'a'\n})\n```\n\n## Tracker Hit Defaults ##\n\nGlobal 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).\n\nThe following example creates a tracker with a default hostname. The two pageviews will track the default hostname and the page path passed in.\n\n```ruby\ntracker = Staccato.tracker('UA-XXXX-Y', client_id, {document_hostname: 'example.com'})\n\ntracker.pageview(path: '/videos/123')\ntracker.pageview(path: '/videos/987')\n```\n\n## Additional Measurements ##\n\nAdditional 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.\n\n**Note:** Certain Measurements require an `index`. This is an integer (usually) between 1 and 200 inclusive.\n\n**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:\n\n* `detail`\n* `click`\n* `add`\n* `remove`\n* `checkout`\n* `checkout_option`\n* `purchase`\n* `refund`\n\n### Transaction w/ Product ###\n\nUsing a pageview to track a transaction with a product (using the 'purchase' as the `product_action`:\n\n```ruby\npageview = tracker.build_pageview(path: '/receipt', hostname: 'mystore.com', title: 'Your Receipt', product_action: 'purchase')\n\npageview.add_measurement(:transaction, {\n  transaction_id: 'T12345',\n  affiliation: 'Your Store',\n  revenue: 37.39,\n  tax: 2.85,\n  shipping: 5.34,\n  currency: 'USD',\n  coupon_code: 'SUMMERSALE'\n})\n\npageview.add_measurement(:product, {\n  index: 1, # this is our first product, value may be 1-200\n  id: 'P12345',\n  name: 'T-Shirt',\n  category: 'Apparel',\n  brand: 'Your Brand',\n  variant: 'Purple',\n  quantity: 2,\n  position: 1,\n  price: 14.60,\n  coupon_code: 'ILUVTEES'\n})\n\npageview.track!\n```\n\n### Transaction Refund ###\n\nThe combination of `product_action: 'refund'` and `transaction` measurement setting a matching `id` to a previous transaction.\n\n```ruby\nevent = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')\n\nevent.add_measurement(:transaction, transaction_id: 'T12345')\n\nevent.track!\n```\n\n### Transaction \u0026 Product Refund ###\n\nThe 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.\n\n```ruby\nevent = tracker.build_event(category: 'order', action: 'refund', non_interactive: true, product_action: 'refund')\n\nevent.add_measurement(:transaction, transaction_id: 'T12345')\nevent.add_measurement(:product, index: 1, id: 'P12345', quantity: 1)\n\nevent.track!\n```\n\n### Promotion Impression ###\n\n```ruby\npageview = tracker.build_pageview(path: '/search', hostname: 'mystore.com', title: 'Search Results')\n\npageview.add_measurement(:promotion, {\n  index: 1,\n  id: 'PROMO_1234',\n  name: 'Summer Sale',\n  creative: 'summer_sale_banner',\n  position: 'banner_1'\n})\n\npageview.track!\n```\n\n### Promotion Click ###\n\nPromotion also supports a `promotion_action`, similar to `product_action`. This is another global option on `Hit`.\n\n```ruby\nevent = tracker.build_event(category: 'promotions', action: 'click', label: 'internal', promotion_action: 'click')\n\nevent.add_measurement(:promotion, {\n  index: 1,\n  id: 'PROMO_1234',\n  name: 'Summer Sale',\n  creative: 'summer_sale_banner',\n  position: 'banner_1'\n})\n\nevent.track!\n```\n\n### Product Click ###\n\n```ruby\nevent = tracker.build_event(category: 'search', action: 'click', label: 'results', product_action: 'click', product_action_list: 'Search Results')\n\nevent.add_measurement(:product, {\n  index: 1,\n  id: 'P12345',\n  name: 'T-Shirt',\n  category: 'Apparel',\n  brand: 'Your Brand',\n  variant: 'Purple',\n  quantity: 2,\n  position: 1,\n  price: 14.60,\n  coupon_code: 'ILUVTEES'\n})\n\nevent.track!\n```\n\n### Checkout ###\n\n```ruby\npageview = tracker.build_pageview(path: '/checkout', hostname: 'mystore.com', title: 'Complete Your Checkout', product_action: 'checkout')\n\npageview.add_measurement(:product, {\n  index: 1, # this is our first product, value may be 1-200\n  id: 'P12345',\n  name: 'T-Shirt',\n  category: 'Apparel',\n  brand: 'Your Brand',\n  variant: 'Purple',\n  quantity: 2,\n  position: 1,\n  price: 14.60,\n  coupon_code: 'ILUVTEES'\n})\n\npageview.add_measurement(:checkout, {\n  step: 1,\n  step_option: 'Visa'\n})\n\npageview.track!\n```\n\n### Checkout Option ###\n\n```ruby\nevent = tracker.build_event(category: 'checkout', action: 'option', non_interactive: true, product_action: 'checkout_option')\n\nevent.add_measurement(:checkout_options, {\n  step: 2,\n  step_option: 'Fedex'\n})\n\nevent.track!\n```\n\n### Impression List \u0026 Product Impression ###\n\n```ruby\npageview = tracker.build_pageview(path: '/home', hostname: 'mystore.com', title: 'Home Page')\n\npageview.add_measurement(:impression_list, index: 1, name: 'Search Results')\n\npageview.add_measurement(:product_impression, {\n  index: 1,\n  list_index: 1, # match the impression_list above\n  id: 'P12345',\n  name: 'T-Shirt',\n  category: 'Apparel',\n  brand: 'Your Brand',\n  variant: 'Purple',\n  position: 1,\n  price: 14.60\n})\n\npageview.add_measurement(:impression_list, index: 2, name: 'Recommendations')\n\npageview.add_measurement(:product_impression, {\n  index: 1,\n  list_index: 2,\n  name: 'Yellow Tee'\n})\n\npageview.add_measurement(:product_impression, {\n  index: 2,\n  list_index: 2,\n  name: 'Red Tee'\n})\n\npageview.track!\n```\n\n### Screenview (as in mobile) ###\n\n```ruby\ntracker.screenview({\n  screen_name: 'user1'\n})\n```\n\n## Google Documentation ##\n\nhttps://developers.google.com/analytics/devguides/collection/protocol/v1/devguide\nhttps://developers.google.com/analytics/devguides/collection/protocol/v1/reference\nhttps://developers.google.com/analytics/devguides/collection/protocol/v1/parameters\n\n## Adapters ##\n\nStaccato provides a variety of adapters for sending or debugging requests being made. To use them, first require the adapter by name: `require 'staccato/adapter/#{chosen-adapter-name}'`\n\nMultiple adapters can be used by calling `add_adapter`:\n\n```ruby\nrequire 'staccato/adapter/validate'\n\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.add_adapter Staccato::Adapter::Validate.new\n  c.add_adapter Staccato::Adapter::Logger.new(Staccato.ga_collection_uri)\n  c.add_adapter Staccato::Adapter::Faraday.new(Staccato.ga_collection_uri)\nend\n```\n\n**Results returned will be in an array, as returned by each adapter in the order the adapters were added.**\n\n### HTTP Adapters ###\n\nStaccato 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.\n\n```ruby\nrequire 'staccato/adapter/faraday' # Faraday gem must be installed\n\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = Staccato::Adapter::Faraday.new(Staccato.ga_collection_uri) do |faraday|\n    # further faraday configuration here\n  end\nend\n```\n\nYou 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.\n\nOne such example might be for a new `net/http` adapter which accepts more options for configuring the connection:\n\n```ruby\nclass CustomAdapter\n  attr_reader :uri\n\n  def initialize(uri, options={})\n    @uri = uri\n    @options = options\n  end\n\n  def post(data)\n    Net::HTTP::Post.new(uri.request_uri).tap do |request|\n      request.read_timeout = @options.fetch(:read_timeout, 90)\n      request.form_data = data\n\n      execute(request)\n    end\n  end\n\n  private\n  def execute(request)\n    Net::HTTP.new(uri.hostname, uri.port).start do |http|\n      http.open_timeout = @options.fetch(:open_timeout, 90)\n      http.request(request)\n    end\n  end\nend\n```\n\nWhich would be used like:\n\n```ruby\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = CustomAdapter.new(Staccato.ga_collection_uri, read_timeout: 1, open_time: 1)\nend\n```\n\n### Validation Adapter ###\n\nThe validation adapter sends hits to the debug endpoint, which responds with information about the validity of the hit.\n\n```ruby\nrequire 'staccato/adapter/validate'\n\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = Staccato::Adapter::Validate.new\nend\n```\n\nSee results by printing a call to track any hit:\n\n```ruby\nputs tracker.pageview(path: '/')\n```\n\nBy default, the staccato `default_adapter` is used to send validation hits, but a different adapter can be used (e.g. `Faraday` or `Net::HTTP`).\n\n```ruby\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = Staccato::Adapter::Validate.new(Staccato::Adapter::HTTP)\nend\n```\n\n### UDP Adapter for Staccato::Proxy ###\n\nIf you're using [Staccato::Proxy](https://github.com/tpitale/staccato-proxy), you can point Staccato at it using the UDP adapter.\n\n```ruby\nrequire 'staccato/adapter/udp'\n\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = Staccato::Adapter::UDP.new(URI('udp://127.0.0.1:3003'))\nend\n```\n\nBe sure to set the ip or host and port to wherever you have configured Staccato::Proxy to run.\n\n### Logger Adapter for Local Development/Debugging ###\n\nIf you're running in development and simply want to make sure Staccato is being called where you expect it to be. Or, if you want to compare the parameters staccato sends with the Google Analytics documentation.\n\n```ruby\nrequire 'staccato/adapter/logger'\n\ntracker = Staccato.tracker('UA-XXXX-Y') do |c|\n  c.adapter = Staccato::Adapter::Logger.new(Staccato.ga_collection_uri, Logger.new(STDOUT), lambda {|params| JSON.dump(params)})\nend\n```\n\nIf you would prefer to log to a file (default is STDOUT), you can pass in an instance of a Logger (from Ruby's stdlib) or anything that responds to `debug`.\n\nIf you would like to format the params hash as something other than `k=v` in your logs, you can pass in anything that responds to `call` and format as a string. The default should be consumable by Splunk and other logging software.\n\n## Image URL for Email Open Tracking ##\n\nAs per [google's docs](https://developers.google.com/analytics/devguides/collection/protocol/v1/email) an `Event` hit type (suggested) may be used to generate an image tag in an email (e.g., as sent by Rails' mailers). This is useful for tracking open stats alongside your other analytics.\n\nThings to keep in mind from Google's suggestions:\n\n1. Hit type should be Event\n2. category should be 'email'\n3. action should be 'open'\n4. document path should be the type of email, e.g., 'welcome' or 'invite', etc must start with a '/' and is advised to include some scope such as '/email' to make it easier to find\n5. document title should be the subject of the email\n6. set the user id, if it is known; NEVER set the user's email\n7. system info fields may be included if known\n\nTo create a url for a hit:\n\n```ruby\nevent = tracker.build_event({\n  category: 'email',\n  action: 'open',\n  document_path: '/email/welcome',\n  document_title: 'Welcome to Our Website!'\n})\n\n# use the image url in your rails template in an image tag\nimage_url = Staccato.as_url(event)\n```\n\n## Contributing ##\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpitale%2Fstaccato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftpitale%2Fstaccato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpitale%2Fstaccato/lists"}