snowplow-tracker-0.6.1/0000755000175000017500000000000013553350203013552 5ustar srudsrudsnowplow-tracker-0.6.1/README.md0000644000175000017500000000655613553350203015045 0ustar srudsrud# Ruby Analytics for Snowplow [![Gem Version](https://badge.fury.io/rb/snowplow-tracker.svg)](http://badge.fury.io/rb/snowplow-tracker) [![Build Status](https://travis-ci.org/snowplow/snowplow-ruby-tracker.png?branch=master)](https://travis-ci.org/snowplow/snowplow-ruby-tracker) [![Code Climate](https://codeclimate.com/github/snowplow/snowplow-ruby-tracker.png)](https://codeclimate.com/github/snowplow/snowplow-ruby-tracker) [![Coverage Status](https://coveralls.io/repos/snowplow/snowplow-ruby-tracker/badge.png)](https://coveralls.io/r/snowplow/snowplow-ruby-tracker) [![License][license-image]][license] ## Overview Add analytics to your Ruby and Rails apps and gems with the **[Snowplow] [snowplow]** event tracker for **[Ruby] [ruby]**. With this tracker you can collect event data from your **[Ruby] [ruby]** applications, **[Ruby on Rails] [rails]** web applications and **[Ruby gems] [rubygems]**. ## Quickstart Assuming git, **[Vagrant] [vagrant-install]** and **[VirtualBox] [virtualbox-install]** installed: ```bash host$ git clone https://github.com/snowplow/snowplow-ruby-tracker.git host$ cd snowplow-ruby-tracker host$ vagrant up && vagrant ssh guest$ cd /vagrant guest$ gem install bundler guest$ bundle install guest$ rspec ``` ## Publishing ```bash host$ vagrant push ``` ## Find out more | Technical Docs | Setup Guide | Roadmap | Contributing | |---------------------------------|---------------------------|-------------------------|-----------------------------------| | ![i1] [techdocs-image] | ![i2] [setup-image] | ![i3] [roadmap-image] | ![i4] [contributing-image] | | **[Technical Docs] [techdocs]** | **[Setup Guide] [setup]** | **[Roadmap] [roadmap]** | **[Contributing] [contributing]** | ## Copyright and license The Snowplow Ruby Tracker is copyright 2013-2016 Snowplow Analytics Ltd. Licensed under the **[Apache License, Version 2.0] [license]** (the "License"); you may not use this software except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. [license-image]: http://img.shields.io/badge/license-Apache--2-blue.svg?style=flat [license]: http://www.apache.org/licenses/LICENSE-2.0 [ruby]: https://www.ruby-lang.org/en/ [rails]: http://rubyonrails.org/ [rubygems]: https://rubygems.org/ [snowplow]: http://snowplowanalytics.com [vagrant-install]: http://docs.vagrantup.com/v2/installation/index.html [virtualbox-install]: https://www.virtualbox.org/wiki/Downloads [techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png [setup-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/setup.png [roadmap-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/roadmap.png [contributing-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/contributing.png [techdocs]: https://github.com/snowplow/snowplow/wiki/Ruby-Tracker [setup]: https://github.com/snowplow/snowplow/wiki/Ruby-Tracker-Setup [roadmap]: https://github.com/snowplow/snowplow/wiki/Ruby-Tracker-Roadmap [contributing]: https://github.com/snowplow/snowplow/wiki/Ruby-Tracker-Contributing snowplow-tracker-0.6.1/snowplow-tracker.gemspec0000644000175000017500000000430713553350203020444 0ustar srudsrud######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: snowplow-tracker 0.6.1 ruby lib Gem::Specification.new do |s| s.name = "snowplow-tracker".freeze s.version = "0.6.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Alexander Dean".freeze, "Fred Blundun".freeze] s.date = "2016-12-26" s.description = "With this tracker you can collect event data from your Ruby applications, Ruby on Rails web applications and Ruby gems.".freeze s.email = "support@snowplowanalytics.com".freeze s.files = ["LICENSE-2.0.txt".freeze, "README.md".freeze, "lib/snowplow-tracker.rb".freeze, "lib/snowplow-tracker/contracts.rb".freeze, "lib/snowplow-tracker/emitters.rb".freeze, "lib/snowplow-tracker/payload.rb".freeze, "lib/snowplow-tracker/self_describing_json.rb".freeze, "lib/snowplow-tracker/subject.rb".freeze, "lib/snowplow-tracker/timestamp.rb".freeze, "lib/snowplow-tracker/tracker.rb".freeze, "lib/snowplow-tracker/version.rb".freeze] s.homepage = "http://github.com/snowplow/snowplow-ruby-tracker".freeze s.licenses = ["Apache License 2.0".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "2.7.6.2".freeze s.summary = "Ruby Analytics for Snowplow".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["<= 0.11", "~> 0.7"]) s.add_development_dependency(%q.freeze, ["~> 2.14.1"]) s.add_development_dependency(%q.freeze, ["~> 1.17.4"]) else s.add_dependency(%q.freeze, ["<= 0.11", "~> 0.7"]) s.add_dependency(%q.freeze, ["~> 2.14.1"]) s.add_dependency(%q.freeze, ["~> 1.17.4"]) end else s.add_dependency(%q.freeze, ["<= 0.11", "~> 0.7"]) s.add_dependency(%q.freeze, ["~> 2.14.1"]) s.add_dependency(%q.freeze, ["~> 1.17.4"]) end end snowplow-tracker-0.6.1/lib/0000755000175000017500000000000013553350203014320 5ustar srudsrudsnowplow-tracker-0.6.1/lib/snowplow-tracker.rb0000644000175000017500000000225113553350203020166 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:snowplow-user@googlegroups.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'snowplow-tracker/contracts.rb' require 'snowplow-tracker/version.rb' require 'snowplow-tracker/self_describing_json.rb' require 'snowplow-tracker/payload.rb' require 'snowplow-tracker/subject.rb' require 'snowplow-tracker/emitters.rb' require 'snowplow-tracker/timestamp.rb' require 'snowplow-tracker/tracker.rb' snowplow-tracker-0.6.1/lib/snowplow-tracker/0000755000175000017500000000000013553350203017641 5ustar srudsrudsnowplow-tracker-0.6.1/lib/snowplow-tracker/emitters.rb0000644000175000017500000002016013553350203022021 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'net/https' require 'set' require 'logger' require 'contracts' module SnowplowTracker LOGGER = Logger.new(STDERR) LOGGER.level = Logger::INFO class Emitter include Contracts @@ConfigHash = ({ :protocol => Maybe[Or['http', 'https']], :port => Maybe[Num], :method => Maybe[Or['get', 'post']], :buffer_size => Maybe[Num], :on_success => Maybe[Func[Num => Any]], :on_failure => Maybe[Func[Num, Hash => Any]], :thread_count => Maybe[Num] }) @@StrictConfigHash = And[@@ConfigHash, lambda { |x| x.class == Hash and Set.new(x.keys).subset? Set.new(@@ConfigHash.keys) }] @@DefaultConfig = { :protocol => 'http', :method => 'get' } Contract String, @@StrictConfigHash => lambda { |x| x.is_a? Emitter } def initialize(endpoint, config={}) config = @@DefaultConfig.merge(config) @lock = Monitor.new @collector_uri = as_collector_uri(endpoint, config[:protocol], config[:port], config[:method]) @buffer = [] if not config[:buffer_size].nil? @buffer_size = config[:buffer_size] elsif config[:method] == 'get' @buffer_size = 1 else @buffer_size = 10 end @method = config[:method] @on_success = config[:on_success] @on_failure = config[:on_failure] LOGGER.info("#{self.class} initialized with endpoint #{@collector_uri}") self end # Build the collector URI from the configuration hash # Contract String, String, Maybe[Num], String => String def as_collector_uri(endpoint, protocol, port, method) port_string = port == nil ? '' : ":#{port.to_s}" path = method == 'get' ? '/i' : '/com.snowplowanalytics.snowplow/tp2' "#{protocol}://#{endpoint}#{port_string}#{path}" end # Add an event to the buffer and flush it if maximum size has been reached # Contract Hash => nil def input(payload) payload.each { |k,v| payload[k] = v.to_s} @lock.synchronize do @buffer.push(payload) if @buffer.size >= @buffer_size flush end end nil end # Flush the buffer # Contract Bool => nil def flush(async=true) @lock.synchronize do send_requests(@buffer) @buffer = [] end nil end # Send all events in the buffer to the collector # Contract ArrayOf[Hash] => nil def send_requests(evts) if evts.size < 1 LOGGER.info("Skipping sending events since buffer is empty") return end LOGGER.info("Attempting to send #{evts.size} request#{evts.size == 1 ? '' : 's'}") evts.each do |event| event['stm'] = (Time.now.to_f * 1000).to_i.to_s # add the sent timestamp, overwrite if already exists end if @method == 'post' post_succeeded = false begin request = http_post(SelfDescribingJson.new( 'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4', evts ).to_json) post_succeeded = is_good_status_code(request.code) rescue StandardError => se LOGGER.warn(se) end if post_succeeded unless @on_success.nil? @on_success.call(evts.size) end else unless @on_failure.nil? @on_failure.call(0, evts) end end elsif @method == 'get' success_count = 0 unsent_requests = [] evts.each do |evt| get_succeeded = false begin request = http_get(evt) get_succeeded = is_good_status_code(request.code) rescue StandardError => se LOGGER.warn(se) end if get_succeeded success_count += 1 else unsent_requests << evt end end if unsent_requests.size == 0 unless @on_success.nil? @on_success.call(success_count) end else unless @on_failure.nil? @on_failure.call(success_count, unsent_requests) end end end nil end # Send a GET request # Contract Hash => lambda { |x| x.is_a? Net::HTTPResponse } def http_get(payload) destination = URI(@collector_uri + '?' + URI.encode_www_form(payload)) LOGGER.info("Sending GET request to #{@collector_uri}...") LOGGER.debug("Payload: #{payload}") http = Net::HTTP.new(destination.host, destination.port) request = Net::HTTP::Get.new(destination.request_uri) if destination.scheme == 'https' http.use_ssl = true end response = http.request(request) LOGGER.add(is_good_status_code(response.code) ? Logger::INFO : Logger::WARN) { "GET request to #{@collector_uri} finished with status code #{response.code}" } response end # Send a POST request # Contract Hash => lambda { |x| x.is_a? Net::HTTPResponse } def http_post(payload) LOGGER.info("Sending POST request to #{@collector_uri}...") LOGGER.debug("Payload: #{payload}") destination = URI(@collector_uri) http = Net::HTTP.new(destination.host, destination.port) request = Net::HTTP::Post.new(destination.request_uri) if destination.scheme == 'https' http.use_ssl = true end request.body = payload.to_json request.set_content_type('application/json; charset=utf-8') response = http.request(request) LOGGER.add(is_good_status_code(response.code) ? Logger::INFO : Logger::WARN) { "POST request to #{@collector_uri} finished with status code #{response.code}" } response end # Only 2xx and 3xx status codes are considered successes # Contract String => Bool def is_good_status_code(status_code) status_code.to_i >= 200 && status_code.to_i < 400 end private :as_collector_uri, :http_get, :http_post end class AsyncEmitter < Emitter Contract String, @@StrictConfigHash => lambda { |x| x.is_a? Emitter } def initialize(endpoint, config={}) @queue = Queue.new() # @all_processed_condition and @results_unprocessed are used to emulate Python's Queue.task_done() @queue.extend(MonitorMixin) @all_processed_condition = @queue.new_cond @results_unprocessed = 0 (config[:thread_count] || 1).times do t = Thread.new do consume end end super(endpoint, config) end def consume loop do work_unit = @queue.pop send_requests(work_unit) @queue.synchronize do @results_unprocessed -= 1 @all_processed_condition.broadcast end end end # Flush the buffer # If async is false, block until the queue is empty # def flush(async=true) loop do @lock.synchronize do @queue.synchronize do @results_unprocessed += 1 end @queue << @buffer @buffer = [] end if not async LOGGER.info('Starting synchronous flush') @queue.synchronize do @all_processed_condition.wait_while { @results_unprocessed > 0 } LOGGER.info('Finished synchronous flush') end end break if @buffer.size < 1 end end end end snowplow-tracker-0.6.1/lib/snowplow-tracker/payload.rb0000644000175000017500000000374613553350203021631 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'base64' require 'json' require 'net/http' require 'contracts' module SnowplowTracker class Payload include Contracts attr_reader :context Contract nil => Payload def initialize @context = {} self end # Add a single name-value pair to @context # Contract String, Or[String, Bool, Num, nil] => Or[String, Bool, Num, nil] def add(name, value) if value != "" and not value.nil? @context[name] = value end end # Add each name-value pair in dict to @context # Contract Hash => Hash def add_dict(dict) for f in dict self.add(f[0], f[1]) end end # Stringify a JSON and add it to @context # Contract Maybe[Hash], Bool, String, String => Maybe[String] def add_json(dict, encode_base64, type_when_encoded, type_when_not_encoded) if dict.nil? return end dict_string = JSON.generate(dict) if encode_base64 self.add(type_when_encoded, Base64.strict_encode64(dict_string)) else self.add(type_when_not_encoded, dict_string) end end end end snowplow-tracker-0.6.1/lib/snowplow-tracker/timestamp.rb0000644000175000017500000000245013553350203022172 0ustar srudsrud# Copyright (c) 2016 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun, Ed Lewis (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2016 Snowplow Analytics Ltd # License:: Apache License Version 2.0 module SnowplowTracker class Timestamp attr_reader :type attr_reader :value def initialize(type, value) @type = type @value = value end end class TrueTimestamp < Timestamp def initialize(value) super 'ttm', value end end class DeviceTimestamp < Timestamp def initialize(value) super 'dtm', value end end endsnowplow-tracker-0.6.1/lib/snowplow-tracker/contracts.rb0000644000175000017500000000226213553350203022170 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'contracts' module SnowplowTracker ORIGINAL_FAILURE_CALLBACK = Contract.method(:failure_callback) def self.disable_contracts Contract.define_singleton_method(:failure_callback) {|data| true} end def self.enable_contracts Contract.define_singleton_method(:failure_callback, ORIGINAL_FAILURE_CALLBACK) end end snowplow-tracker-0.6.1/lib/snowplow-tracker/self_describing_json.rb0000644000175000017500000000211713553350203024342 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 module SnowplowTracker class SelfDescribingJson def initialize(schema, data) @schema = schema @data = data end def to_json { :schema => @schema, :data => @data } end end end snowplow-tracker-0.6.1/lib/snowplow-tracker/tracker.rb0000644000175000017500000002724213553350203021630 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'contracts' require 'securerandom' require 'set' module SnowplowTracker class Tracker include Contracts @@EmitterInput = Or[lambda {|x| x.is_a? Emitter}, ArrayOf[lambda {|x| x.is_a? Emitter}]] @@required_transaction_keys = Set.new(%w(order_id total_value)) @@recognised_transaction_keys = Set.new(%w(order_id total_value affiliation tax_value shipping city state country currency)) @@Transaction = lambda { |x| return false unless x.class == Hash transaction_keys = Set.new(x.keys) @@required_transaction_keys.subset? transaction_keys and transaction_keys.subset? @@recognised_transaction_keys } @@required_item_keys = Set.new(%w(sku price quantity)) @@recognised_item_keys = Set.new(%w(sku price quantity name category context)) @@Item = lambda { |x| return false unless x.class == Hash item_keys = Set.new(x.keys) @@required_item_keys.subset? item_keys and item_keys.subset? @@recognised_item_keys } @@required_augmented_item_keys = Set.new(%w(sku price quantity tstamp order_id)) @@recognised_augmented_item_keys = Set.new(%w(sku price quantity name category context tstamp order_id currency)) @@AugmentedItem = lambda { |x| return false unless x.class == Hash augmented_item_keys = Set.new(x.keys) @@required_augmented_item_keys.subset? augmented_item_keys and augmented_item_keys.subset? @@recognised_augmented_item_keys } @@ContextsInput = ArrayOf[SelfDescribingJson] @@version = TRACKER_VERSION @@default_encode_base64 = true @@base_schema_path = "iglu:com.snowplowanalytics.snowplow" @@schema_tag = "jsonschema" @@context_schema = "#{@@base_schema_path}/contexts/#{@@schema_tag}/1-0-1" @@unstruct_event_schema = "#{@@base_schema_path}/unstruct_event/#{@@schema_tag}/1-0-0" Contract @@EmitterInput, Maybe[Subject], Maybe[String], Maybe[String], Bool => Tracker def initialize(emitters, subject=nil, namespace=nil, app_id=nil, encode_base64=@@default_encode_base64) @emitters = Array(emitters) if subject.nil? @subject = Subject.new else @subject = subject end @standard_nv_pairs = { 'tna' => namespace, 'tv' => @@version, 'aid' => app_id } @config = { 'encode_base64' => encode_base64 } self end # Call subject methods from tracker instance # Subject.instance_methods(false).each do |name| define_method name, ->(*splat) do @subject.method(name.to_sym).call(*splat) self end end # Generates a type-4 UUID to identify this event Contract nil => String def get_event_id() SecureRandom.uuid end # Generates the timestamp (in milliseconds) to be attached to each event # Contract nil => Num def get_timestamp (Time.now.to_f * 1000).to_i end # Builds a self-describing JSON from an array of custom contexts # Contract @@ContextsInput => Hash def build_context(context) SelfDescribingJson.new( @@context_schema, context.map {|c| c.to_json} ).to_json end # Tracking methods # Attaches all the fields in @standard_nv_pairs to the request # Only attaches the context vendor if the event has a custom context # Contract Payload => nil def track(pb) pb.add_dict(@subject.standard_nv_pairs) pb.add_dict(@standard_nv_pairs) pb.add('eid', get_event_id()) @emitters.each{ |emitter| emitter.input(pb.context)} nil end # Log a visit to this page with an inserted device timestamp # Contract String, Maybe[String], Maybe[String], Maybe[@@ContextsInput], Maybe[Num] => Tracker def track_page_view(page_url, page_title=nil, referrer=nil, context=nil, tstamp=nil) if tstamp.nil? tstamp = get_timestamp end track_page_view(page_url, page_title, referrer, context, DeviceTimestamp.new(tstamp)) end # Log a visit to this page # Contract String, Maybe[String], Maybe[String], Maybe[@@ContextsInput], SnowplowTracker::Timestamp => Tracker def track_page_view(page_url, page_title=nil, referrer=nil, context=nil, tstamp=nil) pb = Payload.new pb.add('e', 'pv') pb.add('url', page_url) pb.add('page', page_title) pb.add('refr', referrer) unless context.nil? pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co') end pb.add(tstamp.type, tstamp.value) track(pb) self end # Track a single item within an ecommerce transaction # Not part of the public API # Contract @@AugmentedItem => self def track_ecommerce_transaction_item(argmap) pb = Payload.new pb.add('e', 'ti') pb.add('ti_id', argmap['order_id']) pb.add('ti_sk', argmap['sku']) pb.add('ti_pr', argmap['price']) pb.add('ti_qu', argmap['quantity']) pb.add('ti_nm', argmap['name']) pb.add('ti_ca', argmap['category']) pb.add('ti_cu', argmap['currency']) unless argmap['context'].nil? pb.add_json(build_context(argmap['context']), @config['encode_base64'], 'cx', 'co') end pb.add(argmap['tstamp'].type, argmap['tstamp'].value) track(pb) self end # Track an ecommerce transaction and all the items in it # Set the timestamp as the device timestamp Contract @@Transaction, ArrayOf[@@Item], Maybe[@@ContextsInput], Maybe[Num] => Tracker def track_ecommerce_transaction(transaction, items, context=nil, tstamp=nil) if tstamp.nil? tstamp = get_timestamp end track_ecommerce_transaction(transaction, items, context, DeviceTimestamp.new(tstamp)) end # Track an ecommerce transaction and all the items in it # Contract @@Transaction, ArrayOf[@@Item], Maybe[@@ContextsInput], Timestamp => Tracker def track_ecommerce_transaction(transaction, items, context=nil, tstamp=nil) pb = Payload.new pb.add('e', 'tr') pb.add('tr_id', transaction['order_id']) pb.add('tr_tt', transaction['total_value']) pb.add('tr_af', transaction['affiliation']) pb.add('tr_tx', transaction['tax_value']) pb.add('tr_sh', transaction['shipping']) pb.add('tr_ci', transaction['city']) pb.add('tr_st', transaction['state']) pb.add('tr_co', transaction['country']) pb.add('tr_cu', transaction['currency']) unless context.nil? pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co') end pb.add(tstamp.type, tstamp.value) track(pb) for item in items item['tstamp'] = tstamp item['order_id'] = transaction['order_id'] item['currency'] = transaction['currency'] track_ecommerce_transaction_item(item) end self end # Track a structured event # set the timestamp to the device timestamp Contract String, String, Maybe[String], Maybe[String], Maybe[Num], Maybe[@@ContextsInput], Maybe[Num] => Tracker def track_struct_event(category, action, label=nil, property=nil, value=nil, context=nil, tstamp=nil) if tstamp.nil? tstamp = get_timestamp end track_struct_event(category, action, label, property, value, context, DeviceTimestamp.new(tstamp)) end # Track a structured event # Contract String, String, Maybe[String], Maybe[String], Maybe[Num], Maybe[@@ContextsInput], Timestamp => Tracker def track_struct_event(category, action, label=nil, property=nil, value=nil, context=nil, tstamp=nil) pb = Payload.new pb.add('e', 'se') pb.add('se_ca', category) pb.add('se_ac', action) pb.add('se_la', label) pb.add('se_pr', property) pb.add('se_va', value) unless context.nil? pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co') end pb.add(tstamp.type, tstamp.value) track(pb) self end # Track a screen view event # Contract Maybe[String], Maybe[String], Maybe[@@ContextsInput], Or[Timestamp, Num, nil] => Tracker def track_screen_view(name=nil, id=nil, context=nil, tstamp=nil) screen_view_properties = {} unless name.nil? screen_view_properties['name'] = name end unless id.nil? screen_view_properties['id'] = id end screen_view_schema = "#{@@base_schema_path}/screen_view/#{@@schema_tag}/1-0-0" event_json = SelfDescribingJson.new(screen_view_schema, screen_view_properties) self.track_unstruct_event(event_json, context, tstamp) self end # Better name for track unstruct event # Contract SelfDescribingJson, Maybe[@@ContextsInput], Timestamp => Tracker def track_self_describing_event(event_json, context=nil, tstamp=nil) track_unstruct_event(event_json, context, tstamp) end # Better name for track unstruct event # set the timestamp to the device timestamp Contract SelfDescribingJson, Maybe[@@ContextsInput], Maybe[Num] => Tracker def track_self_describing_event(event_json, context=nil, tstamp=nil) track_unstruct_event(event_json, context, tstamp) end # Track an unstructured event # set the timestamp to the device timstamp Contract SelfDescribingJson, Maybe[@@ContextsInput], Maybe[Num] => Tracker def track_unstruct_event(event_json, context=nil, tstamp=nil) if tstamp.nil? tstamp = get_timestamp end track_unstruct_event(event_json, context, DeviceTimestamp.new(tstamp)) end # Track an unstructured event # Contract SelfDescribingJson, Maybe[@@ContextsInput], Timestamp => Tracker def track_unstruct_event(event_json, context=nil, tstamp=nil) pb = Payload.new pb.add('e', 'ue') envelope = SelfDescribingJson.new(@@unstruct_event_schema, event_json.to_json) pb.add_json(envelope.to_json, @config['encode_base64'], 'ue_px', 'ue_pr') unless context.nil? pb.add_json(build_context(context), @config['encode_base64'], 'cx', 'co') end pb.add(tstamp.type, tstamp.value) track(pb) self end # Flush all events stored in all emitters # Contract Bool => Tracker def flush(async=false) @emitters.each do |emitter| emitter.flush(async) end self end # Set the subject of the events fired by the tracker # Contract Subject => Tracker def set_subject(subject) @subject = subject self end # Add a new emitter # Contract Emitter => Tracker def add_emitter(emitter) @emitters.push(emitter) self end private :get_timestamp, :build_context, :track, :track_ecommerce_transaction_item end end snowplow-tracker-0.6.1/lib/snowplow-tracker/version.rb0000644000175000017500000000166313553350203021661 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 module SnowplowTracker VERSION = '0.6.1' TRACKER_VERSION = "rb-#{VERSION}" end snowplow-tracker-0.6.1/lib/snowplow-tracker/subject.rb0000644000175000017500000000652213553350203021632 0ustar srudsrud# Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved. # # This program is licensed to you under the Apache License Version 2.0, # and you may not use this file except in compliance with the Apache License Version 2.0. # You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. # # Unless required by applicable law or agreed to in writing, # software distributed under the Apache License Version 2.0 is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. # Author:: Alex Dean, Fred Blundun (mailto:support@snowplowanalytics.com) # Copyright:: Copyright (c) 2013-2014 Snowplow Analytics Ltd # License:: Apache License Version 2.0 require 'contracts' module SnowplowTracker class Subject include Contracts @@default_platform = 'srv' @@supported_platforms = ['pc', 'tv', 'mob', 'cnsl', 'iot'] attr_reader :standard_nv_pairs Contract None => Subject def initialize @standard_nv_pairs = {"p" => @@default_platform} self end # Specify the platform # Contract String => Subject def set_platform(value) if @@supported_platforms.include?(value) @standard_nv_pairs['p'] = value else raise "#{value} is not a supported platform" end self end # Set the business-defined user ID for a user # Contract String => Subject def set_user_id(user_id) @standard_nv_pairs['uid'] = user_id self end # Set fingerprint for the user # Contract Num => Subject def set_fingerprint(fingerprint) @standard_nv_pairs['fp'] = fingerprint self end # Set the screen resolution for a device # Contract Num, Num => Subject def set_screen_resolution(width, height) @standard_nv_pairs['res'] = "#{width}x#{height}" self end # Set the dimensions of the current viewport # Contract Num, Num => Subject def set_viewport(width, height) @standard_nv_pairs['vp'] = "#{width}x#{height}" self end # Set the color depth of the device in bits per pixel # Contract Num => Subject def set_color_depth(depth) @standard_nv_pairs['cd'] = depth self end # Set the timezone field # Contract String => Subject def set_timezone(timezone) @standard_nv_pairs['tz'] = timezone self end # Set the language field # Contract String => Subject def set_lang(lang) @standard_nv_pairs['lang'] = lang self end # Set the domain user ID # Contract String => Subject def set_domain_user_id(duid) @standard_nv_pairs['duid'] = duid self end # Set the IP address field # Contract String => Subject def set_ip_address(ip) @standard_nv_pairs['ip'] = ip self end # Set the user agent # Contract String => Subject def set_useragent(ua) @standard_nv_pairs['ua'] = ua self end # Set the network user ID field # This overwrites the nuid field set by the collector # Contract String => Subject def set_network_user_id(nuid) @standard_nv_pairs['tnuid'] = nuid self end end end snowplow-tracker-0.6.1/LICENSE-2.0.txt0000644000175000017500000002613513553350203015701 0ustar srudsrud Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.