New upstream version 12.2.8

This commit is contained in:
Sruthi Chandran 2019-10-16 22:08:35 +05:30
parent fa176ca292
commit 8a1b454a2b
12 changed files with 1332 additions and 0 deletions

View file

@ -0,0 +1,202 @@
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.

View file

@ -0,0 +1,74 @@
# 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

View file

@ -0,0 +1,24 @@
# 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'

View file

@ -0,0 +1,29 @@
# 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

View file

@ -0,0 +1,280 @@
# 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

View file

@ -0,0 +1,73 @@
# 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

View file

@ -0,0 +1,34 @@
# 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

View file

@ -0,0 +1,139 @@
# 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

View file

@ -0,0 +1,46 @@
# 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
end

View file

@ -0,0 +1,371 @@
# 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

View file

@ -0,0 +1,19 @@
# 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

View file

@ -0,0 +1,41 @@
#########################################################
# 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.5.2.1".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<contracts>.freeze, ["<= 0.11", "~> 0.7"])
s.add_development_dependency(%q<rspec>.freeze, ["~> 2.14.1"])
s.add_development_dependency(%q<webmock>.freeze, ["~> 1.17.4"])
else
s.add_dependency(%q<contracts>.freeze, ["<= 0.11", "~> 0.7"])
s.add_dependency(%q<rspec>.freeze, ["~> 2.14.1"])
s.add_dependency(%q<webmock>.freeze, ["~> 1.17.4"])
end
else
s.add_dependency(%q<contracts>.freeze, ["<= 0.11", "~> 0.7"])
s.add_dependency(%q<rspec>.freeze, ["~> 2.14.1"])
s.add_dependency(%q<webmock>.freeze, ["~> 1.17.4"])
end
end