snowplow-tracker-0.8.0/ 0000755 0001751 0001751 00000000000 14400614605 014066 5 ustar pravi pravi snowplow-tracker-0.8.0/LICENSE-2.0.txt 0000644 0001751 0001751 00000026135 14400614605 016215 0 ustar pravi pravi
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. snowplow-tracker-0.8.0/lib/ 0000755 0001751 0001751 00000000000 14400614605 014634 5 ustar pravi pravi snowplow-tracker-0.8.0/lib/snowplow-tracker/ 0000755 0001751 0001751 00000000000 14400614605 020155 5 ustar pravi pravi snowplow-tracker-0.8.0/lib/snowplow-tracker/self_describing_json.rb 0000644 0001751 0001751 00000011436 14400614605 024662 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
module SnowplowTracker
# Creates the self-describing JSONs necessary for sending context or
# self-describing events. These are a specific kind of [JSON
# schema](http://json-schema.org/).
#
# A unique schema can be designed for each type of event or entity (for event
# context) that you want to track. This allows you to track the specific
# things that are important to you, in a way that is defined by you.
#
# A self-describing JSON has two keys, `schema` and `data`. The `schema` value
# should point to a valid self-describing JSON schema. They are called
# self-describing because the schema will specify the fields allowed in the
# `data` value. After events have been collected by the event collector, they
# are validated to ensure that the self-describing JSONs are correct. Mistakes
# (e.g. extra fields, or incorrect types) will result in events being
# processed as Bad Events.
#
# A SelfDescribingJson is initialized with `schema` and `data` as separate
# arguments. These parameters are combined into a complete self-describing
# JSON during the event creation, which is stringified and sent as part of the
# event. By default, they will be sent base64-encoded. This can be changed on
# {Tracker} initialization.
#
# The `data` argument must be a flat hash of key-value pairs. Either strings
# or symbols are accepted as keys. The `schema` argument must be a correctly
# formatted schema ID.
#
# When used to send event context data, stringified self-describing JSONs will
# be sent in the raw event as `cx`, or `co` if not encoded. Whether encoded or
# not, these strings will be converted back to JSON within the `contexts`
# parameter of the processed event. All the event context is contained within
# this one parameter, even if multiple context entities were sent.
#
# Self-describing JSONs in self-describing events are sent in a similar
# manner. They are sent as `ue_px` in the raw event, or `ue_pr` if not
# encoded. This is processed into the `unstruct_event` parameter of the
# finished event.
#
# @example
# # This example schema describes an ad_click event.
# # It defines a single property for that event type, a "bannerId".
#
# {
# "$schema": "http://json-schema.org/schema#",
# "self": {
# "vendor": "com.snowplowanalytics",
# "name": "ad_click",
# "format": "jsonschema",
# "version": "1-0-0"
# },
# "type": "object",
# "properties": {
# "bannerId": {
# "type": "string"
# }
# },
# "required": ["bannerId"],
# "additionalProperties": false
# }
#
# # Creating the SelfDescribingJson
# schema_name = "iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0"
# event_data = { bannerId: "4acd518feb82" }
# SnowplowTracker::SelfDescribingJson.new(schema_name, event_data)
#
# # The self-describing JSON that will be sent (stringified) with the event
# {
# "schema": "iglu:com.snowplowanalytics/ad_click/jsonschema/1-0-0",
# "data": {
# "bannerId": "4acd518feb82"
# }
# }
#
# @api public
# @see
# https://docs.snowplowanalytics.com/docs/understanding-tracking-design/understanding-schemas-and-validation/
# introduction to Snowplow schemas
# @see
# https://docs.snowplowanalytics.com/docs/pipeline-components-and-applications/iglu/
# schema management using Iglu
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol
# the Snowplow Tracker Protocol
class SelfDescribingJson
# @param schema [String] schema identifier
# @param data [Hash] a flat hash that matches the description in the schema
def initialize(schema, data)
@schema = schema
@data = data
end
# make the self-describing JSON out of the instance variables
# @private
def to_json
{
schema: @schema,
data: @data
}
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/payload.rb 0000644 0001751 0001751 00000004126 14400614605 022136 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
require 'base64'
require 'json'
require 'net/http'
module SnowplowTracker
# @private
# Every`track_x_event` method creates a new Payload object. The Tracker then
# uses the Payload instance methods to add properties to the Payload `@data`
# hash. These properties form the raw event, after the completed hash is
# given to the Emitter.
class Payload
attr_reader :data
def initialize
@data = {}
end
# Add a single name-value pair to @data.
def add(name, value)
@data[name] = value if (value != '') && !value.nil?
end
# Add each name-value pair in a hash to @data.
def add_hash(hash)
hash.each { |key, value| add(key, value) }
end
# Stringify a JSON and add it to @data.
#
# In practice, the JSON provided will be a SelfDescribingJson. This method
# is used to add context to events, or for `track_self_describing_event`.
# @see Tracker#track_unstruct_event
# @see Tracker#finalise_payload
def add_json(json, encode_base64, type_when_encoded, type_when_not_encoded)
return if json.nil?
stringified = JSON.generate(json)
if encode_base64
add(type_when_encoded, Base64.strict_encode64(stringified))
else
add(type_when_not_encoded, stringified)
end
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/timestamp.rb 0000644 0001751 0001751 00000011562 14400614605 022512 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
module SnowplowTracker
# Creates timestamps for events. Timestamps are counted in milliseconds since
# the Unix epoch (`(Time.now.to_f * 1000).to_i`).
#
# Snowplow events accrue timestamps as they are processed. When an event is
# first generated by a {Tracker}, it has the raw event property `dtm`
# ("device timestamp"; `dvce_created_tstamp` in the processed event)
# or `ttm` ("true timestamp";`true_tstamp` in the processed event). These two
# timestamps are set using the Timestamp subclasses {DeviceTimestamp} and
# {TrueTimestamp}. The {Emitter} adds a `stm` ("sent timestamp";
# `dvce_sent_tstamp`) property to the event just before sending it to the
# collector. These timestamps are all processed into UTC time.
#
# Events can have either a device timestamp or a true timestamp, not both. By
# default, the `#track_x_event` methods create a DeviceTimestamp. In some
# circumstances, the device timestamp may be inaccurate. There are three
# methods to override the default device timestamp value when the event is
# created.
#
# 1. Provide your own calculated timestamp, as a Num (e.g. `1633596554978`),
# to the `#track_x_event` method. This will be converted into a
# DeviceTimestamp by the Tracker, and will still be recorded as `dtm` in
# the event.
# 2. Manually create a DeviceTimestamp (e.g.
# `SnowplowTracker::DeviceTimestamp.new(1633596554978)`), and provide this to the
# `#track_x_event` method. This will still be recorded as `dtm` in the
# event.
# 3. Provide a TrueTimestamp object to the `track_x_event` method (e.g.
# `SnowplowTracker::TrueTimestamp.new(1633596554978)`). This will result in a `ttm` field in
# the event.
#
# The timestamps that are added to the event once it has been emitted are not
# the responsibility of this class. The collector receives the event and adds a
# `collector_tstamp`. A later part of the pipeline adds the `etl_tstamp` when
# the event enrichment has finished.
#
# When DeviceTimestamp is used, a `derived_tstamp` is also calculated and
# added to the event. This timestamp attempts to take latency and possible
# inaccuracy of the device clock into account. It is calculated by
# `collector_tstamp - (dvce_sent_tstamp - dvce_created_tstamp)`. When
# TrueTimestamp is used, the `derived_stamp` will be the same as
# `true_tstamp`.
#
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol
# the Snowplow Tracker Protocol
# @see
# https://discourse.snowplowanalytics.com/t/which-timestamp-is-the-best-to-see-when-an-event-occurred/538
# A Discourse forums post explaining timestamps
# @api public
class Timestamp
# @private
attr_reader :type
# @private
attr_reader :value
# @private
def initialize(type, value)
@type = type
@value = value
end
# Calculates time since the Unix epoch.
# @private
def self.create
(Time.now.to_f * 1000).to_i
end
end
# @see Timestamp
# A very simple class that stores a timestamp, i.e. a numeric value
# representing milliseconds since the Unix epoch, and which type of timestamp
# it is, namely `ttm`. This raw event `ttm` field will be processed into
# `true_tstamp` in the completed event.
class TrueTimestamp < Timestamp
# @param [Num] value timestamp in milliseconds since the Unix epoch
# @example
# SnowplowTracker::TrueTimestamp.new(1633596346786)
def initialize(value)
super 'ttm', value
end
end
# @see Timestamp
# A very simple class that stores a timestamp, i.e. a numeric value
# representing milliseconds since the Unix epoch, and which type of timestamp
# it is, namely `dtm`. This raw event `dtm` field will be processed into
# `dvce_created_tstamp` in the completed event.
class DeviceTimestamp < Timestamp
# @param [Num] value timestamp in milliseconds since the Unix epoch
# @example
# SnowplowTracker::DeviceTimestamp.new(1633596346786)
def initialize(value)
super 'dtm', value
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/emitters.rb 0000644 0001751 0001751 00000042326 14400614605 022345 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
require 'net/https'
require 'set'
require 'logger'
module SnowplowTracker
# @see Emitter
# For logging Emitter activity messages
LOGGER = Logger.new(STDERR)
LOGGER.level = Logger::INFO
# This class sends events to the event collector. All {Tracker}s must have at
# least one associated Emitter or the subclass AsyncEmitter.
#
# The network settings are defined as part of the Emitter initalization. This
# table displays the default Emitter settings:
#
# | Property | Default setting |
# | --- | --- |
# | Protocol | HTTP |
# | Method | GET |
# | Buffer size | 1 |
# | Path | `/i` |
#
# The buffer size is the number of events which will be buffered before they
# are all sent simultaneously. The process of sending all buffered events is
# called "flushing". The default buffer size is 1 because GET requests can
# only contain one event.
#
# If you choose to use POST requests, the buffer_size defaults to 10, and the
# buffered events are all sent together in a single request. The default path
# is '/com.snowplowanalytics.snowplow/tp2' for Emitters using POST.
#
# # Logging
# Emitters log their activity to STDERR by default, using the Ruby standard
# library Logger class. A different logger can be configured during Emitter
# initialization. For example, to disable logging, you could provide
# `Logger.new(IO::NULL)` in the options hash.
#
# By default, only messages with priority "INFO" or higher will be logged.
# This can be changed at any time for the default logger, which is saved as a
# module constant (`LOGGER = Logger.new(STDERR)`). If you are not using the
# default logger, set the message level before initializing your Emitter.
#
# @see https://ruby-doc.org/stdlib-2.7.2/libdoc/logger/rdoc/Logger.html Logger documentation
#
# @example Changing the logger message level.
# require 'logger'
# SnowplowTracker::LOGGER.level = Logger::DEBUG
class Emitter
# Default Emitter settings
DEFAULT_CONFIG = {
protocol: 'http',
method: 'get'
}
# @private
attr_reader :logger
# Create a new Emitter instance. The endpoint is required.
#
# @example Initializing an Emitter with all the possible extra configuration.
# success_callback = ->(success_count) { puts "#{success_count} events sent successfully" }
# failure_callback = ->(success_count, failures) do
# puts "#{success_count} events sent successfully, #{failures.size} sent unsuccessfully"
# end
#
# SnowplowTracker::Emitter.new(endpoint: 'collector.example.com',
# options: { path: '/my-pipeline/1',
# protocol: 'https',
# port: 443,
# method: 'post',
# buffer_size: 5,
# on_success: success_callback,
# on_failure: failure_callback,
# logger: Logger.new(STDOUT) })
#
# The options hash can have any of these optional parameters:
#
# | Parameter | Description | Type |
# | --- | --- | --- |
# | path | Override the default path for appending to the endpoint | String |
# | protocol | 'http' or 'https' | String |
# | port | The port for the connection | Integer |
# | method | 'get' or 'post' | String |
# | buffer_size | Number of events to send at once | Integer |
# | on_success | A method to call if events were sent successfully | Method |
# | on_failure | A method to call if events did not send | Method |
# | thread_count | Number of threads to use | Integer |
# | logger | Log somewhere other than STDERR | Logger |
#
# Note that `thread_count` is relevant only to the subclass {AsyncEmitter},
# and will be ignored if provided to an Emitter.
#
# If you choose to use HTTPS, we recommend using port 443.
#
# Only 2xx and 3xx status codes are considered successes.
#
# The `on_success` callback should accept one argument: the number of
# requests sent this way. The `on_failure` callback should accept two
# arguments: the number of successfully sent events, and an array containing
# the unsuccessful events.
#
# @param endpoint [String] the endpoint to send the events to
# @param options [Hash] allowed configuration options
#
# @see AsyncEmitter#initialize
# @api public
def initialize(endpoint:, options: {})
config = DEFAULT_CONFIG.merge(options)
@lock = Monitor.new
path = confirm_path(config)
@collector_uri = create_collector_uri(endpoint, config[:protocol], config[:port], path)
@buffer = []
@buffer_size = confirm_buffer_size(config)
@method = config[:method]
@on_success = config[:on_success]
@on_failure = config[:on_failure]
@logger = config[:logger] || LOGGER
logger.info("#{self.class} initialized with endpoint #{@collector_uri}")
end
# Creates the `@buffer_size` variable during initialization. Unless
# otherwise defined, it's 1 for Emitters using GET and 10 for Emitters using
# POST requests.
# @private
def confirm_buffer_size(config)
return config[:buffer_size] unless config[:buffer_size].nil?
config[:method] == 'get' ? 1 : 10
end
# Creates the `@path` variable during initialization. Allows a non-standard
# path to be provided.
# @private
def confirm_path(config)
return config[:path] unless config[:path].nil?
config[:method] == 'get' ? '/i' : '/com.snowplowanalytics.snowplow/tp2'
end
# Creates the `@collector_uri` variable during initialization.
# The default is "http://{endpoint}/i".
# @private
def create_collector_uri(endpoint, protocol, port, path)
port_string = port.nil? ? '' : ":#{port}"
"#{protocol}://#{endpoint}#{port_string}#{path}"
end
# Add an event to the buffer and flush it if maximum size has been reached.
# This method is not required for standard Ruby tracker usage. A {Tracker}
# privately calls this method once the event payload is ready to send.
#
# We have included it as part of the public API for its possible use in the
# `on_failure` callback. This is the optional method, provided in the
# `options` Emitter initalization hash, that is called when events fail
# to send. You could use {#input} as part of your callback to immediately
# retry the failed event.
#
# The `on_failure` callback should accept two arguments: the number of
# successfully sent events, and an array containing the unsuccessful events.
#
# @example A possible `on_failure` method using `#input`
# def retry_on_failure(failed_event_count, failed_events)
# # possible backoff-and-retry timeout here
# failed_events.each do |event|
# my_emitter.input(event)
# end
# end
#
# @api public
def input(payload)
payload.each { |k, v| payload[k] = v.to_s }
@lock.synchronize do
@buffer.push(payload)
flush if @buffer.size >= @buffer_size
end
nil
end
# Flush the Emitter, forcing it to send all the events in its
# buffer, even if the buffer is not full. {Emitter} objects, unlike
# {AsyncEmitter}s, can only `flush` synchronously. A {Tracker} can manually flush all
# its Emitters by calling {Tracker#flush}, part of the public API which
# calls this method.
#
# The unused async parameter here is to avoid ArgumentError, since
# {AsyncEmitter#flush} does take an argument.
#
# @see AsyncEmitter#flush
# @private
def flush(_async = true)
@lock.synchronize do
send_requests(@buffer)
@buffer = []
end
nil
end
# Send all events in the buffer to the collector
# @private
def send_requests(events)
if events.empty?
logger.info('Skipping sending events since buffer is empty')
return
end
logger.info("Attempting to send #{events.size} request#{events.size == 1 ? '' : 's'}")
events.each do |event|
# add the sent timestamp, overwrite if already exists
event['stm'] = Timestamp.create.to_s
end
if @method == 'post'
send_requests_with_post(events)
elsif @method == 'get'
send_requests_with_get(events)
end
nil
end
# Part of {#send_requests}.
# @private
def send_requests_with_post(events)
post_succeeded = false
begin
request = http_post(SelfDescribingJson.new(
'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4',
events
).to_json)
post_succeeded = good_status_code?(request.code)
rescue StandardError => standard_error
logger.warn(standard_error)
end
if post_succeeded
@on_success.call(events.size) unless @on_success.nil?
else
@on_failure.call(0, events) unless @on_failure.nil?
end
nil
end
# Part of {#send_requests}.
# @private
def send_requests_with_get(events)
success_count = 0
unsent_requests = []
events.each do |event|
request = process_get_event(event)
request ? success_count += 1 : unsent_requests << event
end
if unsent_requests.size.zero?
@on_success.call(success_count) unless @on_success.nil?
else
@on_failure.call(success_count, unsent_requests) unless @on_failure.nil?
end
nil
end
# Part of {#send_requests_with_get}.
# @private
def process_get_event(event)
get_succeeded = false
begin
request = http_get(event)
get_succeeded = good_status_code?(request.code)
rescue StandardError => standard_error
logger.warn(standard_error)
end
get_succeeded
end
# Part of {#process_get_event}. This sends a GET request.
# @private
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)
http.use_ssl = true if destination.scheme == 'https'
response = http.request(request)
logger.add(good_status_code?(response.code) ? Logger::INFO : Logger::WARN) do
"GET request to #{@collector_uri} finished with status code #{response.code}"
end
response
end
# Part of {#send_requests_with_post}. This sends a POST request.
# @private
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)
http.use_ssl = true if destination.scheme == 'https'
request.body = payload.to_json
request.set_content_type('application/json; charset=utf-8')
response = http.request(request)
logger.add(good_status_code?(response.code) ? Logger::INFO : Logger::WARN) do
"POST request to #{@collector_uri} finished with status code #{response.code}"
end
response
end
# Check if the response is good.
# Only 2xx and 3xx status codes are considered successes.
# @private
def good_status_code?(status_code)
status_code.to_i >= 200 && status_code.to_i < 400
end
private :create_collector_uri,
:http_get,
:http_post
end
# This {Emitter} subclass provides asynchronous event sending. Whenever the
# buffer is flushed, the AsyncEmitter places the flushed events in a work
# queue. The AsyncEmitter asynchronously sends events in this queue using a
# thread pool of a fixed size. The size of the thread pool is 1 by default,
# but can be configured as part of the options hash during initialization.
#
# @see Emitter
# @api public
class AsyncEmitter < Emitter
# Create a new AsyncEmitter object. The endpoint is required.
#
# @example Initializing an AsyncEmitter with all the possible extra configuration.
# success_callback = ->(success_count) { puts "#{success_count} events sent successfully" }
# failure_callback = ->(success_count, failures) do
# puts "#{success_count} events sent successfully, #{failures.size} sent unsuccessfully"
# end
#
# SnowplowTracker::Emitter.new(endpoint: 'collector.example.com',
# options: { path: '/my-pipeline/1',
# protocol: 'https',
# port: 443,
# method: 'post',
# buffer_size: 5,
# on_success: success_callback,
# on_failure: failure_callback,
# logger: Logger.new(STDOUT),
# thread_count: 5 })
#
# The options hash can have any of these optional parameters:
#
# | Parameter | Description | Type |
# | --- | --- | --- |
# | path | Override the default path for appending to the endpoint | String |
# | protocol | 'http' or 'https' | String |
# | port | The port for the connection | Integer |
# | method | 'get' or 'post' | String |
# | buffer_size | Number of events to send at once | Integer |
# | on_success | A function to call if events were sent successfully | Function |
# | on_failure | A function to call if events did not send | Function |
# | thread_count | Number of threads to use | Integer |
# | logger | Log somewhere other than STDERR | Logger |
#
# The `thread_count` determines the number of worker threads which will be
# used to send events.
#
# If you choose to use HTTPS, we recommend using port 443.
#
# Only 2xx and 3xx status codes are considered successes.
#
# The `on_success` callback should accept one argument: the number of
# requests sent this way. The `on_failure` callback should accept two
# arguments: the number of successfully sent events, and an array containing
# the unsuccessful events.
#
# @note if you test the AsyncEmitter by using a short script to send an
# event, you may find that the event fails to send. This is because the
# process exits before the flushing thread is finished. You can get round
# this either by adding a sleep(10) to the end of your script or by using
# the synchronous flush.
#
# @param endpoint [String] the endpoint to send the events to
# @param options [Hash] allowed configuration options
#
# @see Emitter#initialize
# @api public
def initialize(endpoint:, options: {})
@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
(options[:thread_count] || 1).times { Thread.new { consume } }
super(endpoint: endpoint, options: options)
end
# AsyncEmitters use the MonitorMixin module, which provides the
# `synchronize` and `broadcast` methods.
# @private
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 Emitter, forcing it to send all the events in its buffer, even
# if the buffer is not full.
#
# If `async` is true (the default), events are sent even if the queue is not
# empty. If `async` is false, it blocks until all queued events have been
# sent. Note that this method can be called by public API method
# {Tracker#flush}, which has a default of `async` being false.
#
# @param async [Bool] whether to flush asynchronously or not
#
# @see Emitter#flush
# @private
def flush(async = true)
loop do
@lock.synchronize do
@queue.synchronize { @results_unprocessed += 1 }
@queue << @buffer
@buffer = []
end
unless 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.empty?
end
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/version.rb 0000644 0001751 0001751 00000003442 14400614605 022172 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
# The Snowplow Ruby Tracker allows you to track Snowplow events in your Ruby
# applications and gems and Ruby on Rails web applications.
#
# It is compatible with Ruby versions 2.1 to 3.0+.
#
# See the [demo Rails
# app](https://github.com/snowplow-incubator/snowplow-ruby-tracker-examples) to
# see an example of how to incorporate the Snowplow Ruby tracker in Ruby on
# Rails app.
#
# @see https://github.com/snowplow/snowplow-ruby-tracker
# Ruby tracker on Github
# @see https://github.com/snowplow-incubator/snowplow-ruby-tracker-examples
# Snowplow Ruby tracker examples in a demo Rails app
# @see https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/
# Snowplow documentation
# @see https://snowplowanalytics.com/ Snowplow homepage
# @api public
module SnowplowTracker
# The version of Ruby Snowplow tracker you are using
VERSION = '0.8.0'
# All events from this tracker will have this string
TRACKER_VERSION = "rb-#{VERSION}"
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/tracker.rb 0000644 0001751 0001751 00000061700 14400614605 022141 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
require 'securerandom'
require 'set'
module SnowplowTracker
# Allows the tracking of events. The tracker accepts event properties to its
# various `track_x_event` methods, and creates an appropriate event payload.
# This payload is passed to one or more Emitters for sending to the event
# collector.
#
# A Tracker is always associated with one {Subject}, and one or more
# {Emitter}. The Subject object stores information about the user, and will be
# generated automatically if one is not provided during initialization. It can
# be swapped out for another Subject using {#set_subject}.
#
# Tracker objects can access the methods of their associated {Subject}, e.g.
# {#set_user_id}.
#
# The Emitter, or an array of Emitters, must be given during initialization.
# They will send the prepared events to the event collector. It's possible to
# add further Emitters to an existing Tracker, using {#add_emitter}. However,
# Emitters cannot be removed from Trackers.
#
# At initialization, two Tracker parameters can be set which will be added to
# all events. The first is the Tracker namespace. This is especially useful to
# distinguish between events from different Trackers, if more than one is
# being used. The namespace value will be sent as the `tna` field in the raw
# event, mapping to `name_tracker` in the processed event.
#
# The second user-set Tracker property is the app ID (`aid`; `app_id`). This
# is the unique identifier for the site or application, and is particularly
# useful for distinguishing between events when Snowplow tracking has been
# implemented in multiple apps.
#
# The final initialization parameter is a setting for the base64-encoding of
# any JSONs in the event payload. These will be the {SelfDescribingJson}s used
# to provide context to events, or in the {#track_self_describing_event}
# method. The default is for JSONs to be encoded. Once the Tracker has been
# instantiated, it is not possible to change this setting.
#
# # Tracking events
#
# The Tracker `#track_x_event` methods all work similarly. An event payload is
# created containing the relevant properties, which is passed to an {Emitter}
# for sending. All payloads have a unique event ID (`event_id`) added to them
# (a type-4 UUID created using the SecureRandom module). This is sent as the
# `eid` field in the raw event.
#
# The Ruby tracker provides the ability to track multiple types of events
# out-of-the-box. The `#track_x_event` methods range from single purpose
# methods, such as {#track_page_view}, to the more complex but flexible
# {#track_self_describing_event}, which can be used to track any kind of
# event. We strongly recommend using {#track_self_describing_event} for your
# tracking, as it allows you to design custom event types to match your
# business requirements.
#
# This table gives the event type in the raw and processed events, defined in
# the Snowplow Tracker Protocol. This is the `e` or `event` parameter. Note
# that {#track_screen_view} calls {#track_self_describing_event} behind the
# scenes, resulting in a `ue` event type.
#
#
#
# | Tracker method | `e` (raw) | `event` (processed) |
# | --- | --- | --- |
# | {#track_self_describing_event} | `ue` | `unstruct` |
# | {#track_struct_event} | `se` | `struct` |
# | {#track_page_view} | `pv` | `page_view` |
# | {#track_ecommerce_transaction} | `tr` and `ti` | `transaction` and `transaction_item` |
# | {#track_screen_view} | `ue` | `unstruct` |
#
#
#
# The name `ue`, "unstructured event", is partially depreciated. This event
# type was originally created as a counterpart to "structured event", but the
# name is misleading. An `unstruct` event requires a schema ruleset and
# therefore can be considered more structured than a `struct` event. We prefer
# the name "self-describing event", after the {SelfDescribingJson} schema.
# Changing the event name in the Tracker Protocol would be a breaking change,
# so for now the self-describing events are still sent as "unstruct".
#
# All the `#track_x_event` methods share common features and parameters. Every
# type of event can have an optional context, {Subject}, and {Page} added. A
# {Timestamp} can also be provided for all event types to override the default
# event timestamp.
#
# [Event
# context](https://docs.snowplowanalytics.com/docs/understanding-tracking-design/understanding-events-entities/)
# can be provided as an array of {SelfDescribingJson}. Each element of the
# array is called an entity. Contextual entities can be used to describe the
# setting in which the event occurred. For example, a "user" entity could be
# created and attached to all events from each user. For a search event,
# entities could be attached for each of the search results. The Ruby tracker
# does not automatically add any event context. This is in contrast to the
# [Snowplow JavaScript Tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/),
# which automatically attaches a "webpage" entity to every event that it tracks,
# containing a unique ID for that loaded page.
#
# @see Subject
# @see Emitter
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol
# the Snowplow Tracker Protocol
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/tracking-events/
# the Snowplow docs page about tracking events
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/enriching-your-events/
# the Snowplow docs page about adding context and other extra data to events
# @see
# https://docs.snowplowanalytics.com/docs/understanding-tracking-design/introduction-to-tracking-design/
# introduction to Snowplow tracking design
# @api public
class Tracker
# @!group Public constants
# SelfDescribingJson objects are sent encoded by default
DEFAULT_ENCODE_BASE64 = true
# @private
BASE_SCHEMA_PATH = 'iglu:com.snowplowanalytics.snowplow'
# @private
SCHEMA_TAG = 'jsonschema'
# @private
CONTEXT_SCHEMA = "#{BASE_SCHEMA_PATH}/contexts/#{SCHEMA_TAG}/1-0-1"
# @private
UNSTRUCT_EVENT_SCHEMA = "#{BASE_SCHEMA_PATH}/unstruct_event/#{SCHEMA_TAG}/1-0-0"
# @private
SCREEN_VIEW_SCHEMA = "#{BASE_SCHEMA_PATH}/screen_view/#{SCHEMA_TAG}/1-0-0"
# @!endgroup
# Create a new Tracker. `emitters` is the only strictly required parameter.
#
# @param emitters [Emitter, Array] one or more Emitter objects
# @param subject [Subject] a Subject object
# @param namespace [String] a name for the Tracker
# @param app_id [String] the app ID
# @param encode_base64 [Bool] whether JSONs will be base64-encoded or not
# @example Initializing a Tracker with all possible options
# SnowplowTracker::Tracker.new(
# emitters: SnowplowTracker::Emitter.new(endpoint: 'collector.example.com'),
# subject: SnowplowTracker::Subject.new,
# namespace: 'tracker_no_encode',
# app_id: 'rails_main',
# encode_base64: false
# )
# @api public
#
# @note All the Tracker instance methods return the Tracker object, allowing
# method chaining, e.g.
# `SnowplowTracker::Tracker.new.set_user_id('12345').track_page_view(page_url: 'www.example.com`
def initialize(emitters:, subject: nil, namespace: nil, app_id: nil, encode_base64: DEFAULT_ENCODE_BASE64)
@emitters = Array(emitters)
@subject = if subject.nil?
Subject.new
else
subject
end
@settings = {
'tna' => namespace,
'tv' => TRACKER_VERSION,
'aid' => app_id
}
@encode_base64 = encode_base64
end
# @!method set_color_depth(depth)
# call {Subject#set_color_depth}
# @!method set_domain_session_id(sid)
# call {Subject#set_domain_session_id}
# @!method set_domain_session_idx(vid)
# call {Subject#set_domain_session_idx}
# @!method set_domain_user_id(duid)
# call {Subject#set_domain_user_id}
# @!method set_fingerprint(fingerprint)
# call {Subject#set_fingerprint}
# @!method set_ip_address(ip)
# call {Subject#set_ip_address}
# @!method set_lang(lang)
# call {Subject#set_lang}
# @!method set_network_user_id(nuid)
# call {Subject#set_network_user_id}
# @!method set_platform(platform)
# call {Subject#set_platform}
# @!method set_screen_resolution(width:, height:)
# call {Subject#set_screen_resolution}
# @!method set_timezone(timezone)
# call {Subject#set_timezone}
# @!method set_user_id(user_id)
# call {Subject#set_user_id}
# @!method set_useragent(useragent)
# call {Subject#set_useragent}
# @!method set_viewport(width:, height:)
# call {Subject#set_viewport}
Subject.instance_methods(false).each do |name|
if RUBY_VERSION >= '3.0.0'
define_method name, ->(*args, **kwargs) do
@subject.method(name.to_sym).call(*args, **kwargs)
self
end
else
define_method name, ->(*args) do
@subject.method(name.to_sym).call(*args)
self
end
end
end
# Generates a type-4 UUID to identify this event
# @private
def event_id
SecureRandom.uuid
end
# Builds a single self-describing JSON from an array of custom contexts
# @private
def build_context(context)
SelfDescribingJson.new(
CONTEXT_SCHEMA,
context.map(&:to_json)
).to_json
end
# Sends the payload hash as a request to the Emitter(s)
# @private
def track(payload)
@emitters.each { |emitter| emitter.input(payload.data) }
nil
end
# Ensures that either a DeviceTimestamp or TrueTimestamp is associated with
# every event.
# @private
def process_tstamp(tstamp)
tstamp = Timestamp.create if tstamp.nil?
tstamp = DeviceTimestamp.new(tstamp) if tstamp.is_a? Numeric
tstamp
end
# Attaches the more generic fields to the event payload. This includes
# context, Subject, and Page if they are present. The timestamp is added, as
# well as all fields from `@settings`.
#
# Finally, the Tracker generates and attaches an event ID.
# @private
def finalise_payload(payload, context, tstamp, event_subject, page)
payload.add_json(build_context(context), @encode_base64, 'cx', 'co') unless context.nil? || context.empty?
payload.add_hash(page.details) unless page.nil?
if event_subject.nil?
payload.add_hash(@subject.details)
else
payload.add_hash(@subject.details.merge(event_subject.details))
end
payload.add(tstamp.type, tstamp.value)
payload.add_hash(@settings)
payload.add('eid', event_id)
nil
end
# Track a visit to a page.
# @example
# SnowplowTracker::Tracker.new.track_page_view(page_url: 'www.example.com',
# page_title: 'example',
# referrer: 'www.referrer.com')
#
# @param page_url [String] the URL of the page
# @param page_title [String] the page title
# @param referrer [String] the URL of the referrer page
# @param context [Array] an array of SelfDescribingJson objects
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
# @param subject [Subject] event-specific Subject object
# @param page [Page] override the page_url, page_title, or referrer
#
# @api public
def track_page_view(page_url:, page_title: nil, referrer: nil,
context: nil, tstamp: nil, subject: nil, page: nil)
tstamp = process_tstamp(tstamp)
payload = Payload.new
payload.add('e', 'pv')
payload.add('url', page_url)
payload.add('page', page_title)
payload.add('refr', referrer)
finalise_payload(payload, context, tstamp, subject, page)
track(payload)
self
end
# Track an eCommerce transaction, and all the items in it.
#
# This method is unique in sending multiple events: one `transaction` event,
# and one `transaction_item` event for each item. If Subject or Page objects
# are provided, their parameters will be merged into both `transaction` and
# `transaction_item` events. The timestamp and event ID of the
# `transaction_item` events will always be the same as the `transaction`.
# Transaction items are also automatically populated with the `order_id` and
# `currency` fields from the transaction.
#
# Event context is handled differently for `transaction` and
# `transaction_item` events. A context array argument provided to this
# method will be attached to the `transaction` event only. To attach a
# context array to a transaction item, use the key "context" in the item
# hash.
#
# The transaction and item hash arguments must contain the correct keys, as
# shown in the tables below.
#
# | Transaction fields | Description | Required? | Type |
# | --- | --- | --- | --- |
# | order_id | ID of the eCommerce transaction | Yes | String |
# | total_value | Total transaction value | Yes | Num |
# | affiliation | Transaction affiliation | No | String |
# | tax_value | Transaction tax value | No | Num |
# | shipping | Delivery cost charged | No | Num |
# | city | Delivery address city | No | String |
# | state | Delivery address state | No | String |
# | country | Delivery address country | No | String |
# | currency | Transaction currency | No | String |
#
#
#
# | Item fields | Description | Required? | Type |
# | --- | --- | --- | --- |
# | sku | Item SKU | Yes | String |
# | price | Item price | Yes | Num |
# | quantity | Item quantity | Yes | Integer |
# | name | Item name | No | String |
# | category | Item category | No | String |
# | context | Item event context | No | Array[{SelfDescribingJson}] |
#
# @example Tracking a transaction containing two items
# SnowplowTracker::Tracker.new.track_ecommerce_transaction(
# transaction: {
# 'order_id' => '12345',
# 'total_value' => 49.99,
# 'affiliation' => 'my_company',
# 'tax_value' => 0,
# 'shipping' => 0,
# 'city' => 'Phoenix',
# 'state' => 'Arizona',
# 'country' => 'USA',
# 'currency' => 'USD'
# },
# items: [
# {
# 'sku' => 'pbz0026',
# 'price' => 19.99,
# 'quantity' => 1
# },
# {
# 'sku' => 'pbz0038',
# 'price' => 15,
# 'quantity' => 2,
# 'name' => 'crystals',
# 'category' => 'magic'
# }
# ]
# )
#
# @param transaction [Hash] the correctly structured transaction hash
# @param items [Array] an array of correctly structured item hashes
# @param context [Array] an array of SelfDescribingJson objects
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
# @param subject [Subject] event-specific Subject object
# @param page [Page] event-specific Page object
#
# @api public
def track_ecommerce_transaction(transaction:, items:,
context: nil, tstamp: nil,
subject: nil, page: nil)
tstamp = process_tstamp(tstamp)
transform_keys(transaction)
payload = Payload.new
payload.add('e', 'tr')
payload.add('tr_id', transaction['order_id'])
payload.add('tr_tt', transaction['total_value'])
payload.add('tr_af', transaction['affiliation'])
payload.add('tr_tx', transaction['tax_value'])
payload.add('tr_sh', transaction['shipping'])
payload.add('tr_ci', transaction['city'])
payload.add('tr_st', transaction['state'])
payload.add('tr_co', transaction['country'])
payload.add('tr_cu', transaction['currency'])
finalise_payload(payload, context, tstamp, subject, page)
track(payload)
items.each do |item|
transform_keys(item)
item['tstamp'] = tstamp
item['order_id'] = transaction['order_id']
item['currency'] = transaction['currency']
track_ecommerce_transaction_item(item, subject, page)
end
self
end
# Makes sure all hash keys are strings rather than symbols.
# The Ruby core language added a method for this in Ruby 2.5.
# @private
def transform_keys(hash)
hash.keys.each { |key| hash[key.to_s] = hash.delete key }
end
# Track a single item within an ecommerce transaction.
# @private
def track_ecommerce_transaction_item(details, subject, page)
payload = Payload.new
payload.add('e', 'ti')
payload.add('ti_id', details['order_id'])
payload.add('ti_sk', details['sku'])
payload.add('ti_pr', details['price'])
payload.add('ti_qu', details['quantity'])
payload.add('ti_nm', details['name'])
payload.add('ti_ca', details['category'])
payload.add('ti_cu', details['currency'])
finalise_payload(payload, details['context'], details['tstamp'], subject, page)
track(payload)
self
end
# Track a structured event. `category` and `action` are required.
#
# This event type can be used to track many types of user activity, as it is
# somewhat customizable. This event type is provided particularly for
# concordance with Google Analytics tracking, where events are structured by
# "category", "action", "label", and "value".
#
# For fully customizable event tracking, we recommend you use
# self-describing events.
#
# @example
# SnowplowTracker::Tracker.new.track_struct_event(
# category: 'shop',
# action: 'add-to-basket',
# property: 'pcs',
# value: 2
# )
#
#
# @see #track_self_describing_event
#
# @param category [String] the event category
# @param action [String] the action performed
# @param label [String] an event label
# @param property [String] an event property
# @param value [Num] a value for the event
# @param context [Array] an array of SelfDescribingJson objects
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
# @param subject [Subject] event-specific Subject object
# @param page [Page] event-specific Page object
#
# @api public
def track_struct_event(category:, action:, label: nil, property: nil,
value: nil, context: nil, tstamp: nil, subject: nil, page: nil)
tstamp = process_tstamp(tstamp)
payload = Payload.new
payload.add('e', 'se')
payload.add('se_ca', category)
payload.add('se_ac', action)
payload.add('se_la', label)
payload.add('se_pr', property)
payload.add('se_va', value)
finalise_payload(payload, context, tstamp, subject, page)
track(payload)
self
end
# Track a screen view event. Note that while the `name` and `id` parameters
# are both optional, you must provided at least one of them to create a
# valid event.
#
# This method creates an `unstruct` event, by creating a
# {SelfDescribingJson} and calling {#track_self_describing_event}. The
# schema ID for this is
# "iglu:com.snowplowanalytics.snowplow/screen_view/jsonschema/1-0-0", and
# the data field will contain the name and/or ID.
#
# @example
# SnowplowTracker::Tracker.new.track_screen_view(name: 'HUD > Save Game',
# id: 'screen23')
#
#
# @see #track_page_view
# @see #track_self_describing_event
#
# @param name [String] the screen name (human readable)
# @param id [String] the unique screen ID
# @param context [Array] an array of SelfDescribingJson objects
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
# @param subject [Subject] event-specific Subject object
# @param page [Page] event-specific Page object
#
# @api public
def track_screen_view(name: nil, id: nil, context: nil, tstamp: nil, subject: nil, page: nil)
screen_view_properties = {}
screen_view_properties['name'] = name unless name.nil?
screen_view_properties['id'] = id unless id.nil?
event_json = SelfDescribingJson.new(SCREEN_VIEW_SCHEMA, screen_view_properties)
track_unstruct_event(event_json: event_json, context: context,
tstamp: tstamp, subject: subject, page: page)
self
end
# Track a self-describing event. These are custom events based on
# {SelfDescribingJson}, i.e. a JSON schema and a defined set of properties.
#
# This is useful for tracking specific or proprietary event types, or events
# with unpredicable or frequently changing properties.
#
# This method creates an `unstruct` event type. It is actually an alias for
# {#track_unstruct_event}, which is depreciated due to its unhelpful name.
#
# @example
# self_desc_json = SnowplowTracker::SelfDescribingJson.new(
# "iglu:com.example_company/save_game/jsonschema/1-0-2",
# {
# "saveId" => "4321",
# "level" => 23,
# "difficultyLevel" => "HARD",
# "dlContent" => true
# }
# )
#
# SnowplowTracker::Tracker.new.track_self_describing_event(event_json: self_desc_json)
#
#
# @param event_json [SelfDescribingJson] a SelfDescribingJson object
# @param context [Array] an array of SelfDescribingJson objects
# @param tstamp [DeviceTimestamp, TrueTimestamp, Num] override the default DeviceTimestamp of the event
# @param subject [Subject] event-specific Subject object
# @param page [Page] event-specific Page object
#
# @api public
def track_self_describing_event(event_json:, context: nil, tstamp: nil, subject: nil, page: nil)
track_unstruct_event(event_json: event_json, context: context,
tstamp: tstamp, subject: subject, page: page)
end
# @deprecated Use {#track_self_describing_event} instead.
#
# @api public
def track_unstruct_event(event_json:, context: nil, tstamp: nil, subject: nil, page: nil)
tstamp = process_tstamp(tstamp)
payload = Payload.new
payload.add('e', 'ue')
envelope = SelfDescribingJson.new(UNSTRUCT_EVENT_SCHEMA, event_json.to_json)
payload.add_json(envelope.to_json, @encode_base64, 'ue_px', 'ue_pr')
finalise_payload(payload, context, tstamp, subject, page)
track(payload)
self
end
# Manually flush all events stored in all Tracker-associated Emitters. By
# default, this happens synchronously. {Emitter}s can only send events
# synchronously, while {AsyncEmitter}s can send either synchronously or
# asynchronously.
#
# @param async [Bool] whether to flush asynchronously or not
#
# @api public
def flush(async: false)
@emitters.each do |emitter|
emitter.flush(async)
end
self
end
# Replace the existing Tracker-associated Subject with the provided one. All
# subsequent events will have the properties of the new Subject, unless they
# are overriden by event-specific Subject parameters.
#
# @param subject [Subject] a Subject object
#
# @api public
def set_subject(subject)
@subject = subject
self
end
# Add a new Emitter to the internal array of Tracker-associated Emitters.
#
# @param emitter [Emitter] an Emitter object
#
# @api public
def add_emitter(emitter)
@emitters.push(emitter)
self
end
private :build_context,
:track,
:track_ecommerce_transaction_item
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/subject.rb 0000644 0001751 0001751 00000034721 14400614605 022150 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
module SnowplowTracker
# Subject objects store information about the user associated with the event,
# such as their `user_id`, what type of device they used, or what size screen
# that device had. Also, they store which platform the event occurred on -
# e.g. server-side app, mobile, games console, etc.
#
# Subject parameters are saved into the tracked event as part of the 'atomic'
# event properties, which have their own column in the eventual table of
# events. For example, a Subject's `user_id` parameter will be sent as `uid`
# in the raw event payload, ending up in the `user_id` column. These
# parameters represent part of the [Snowplow Tracker
# Protocol](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol),
# which defines a Snowplow event.
#
# Each {Tracker} is initialized with a Subject. This means that every event by
# default has the platform (`p` parameter in the raw event) `srv`: server-side
# app. Platform is the only preset Subject parameter, which can be overriden
# using the {#set_platform} method. All other parameters must be set manually.
# This can be done directly on the Subject, or, if it is associated with a
# Tracker, via the Tracker, which has access to all the methods of its
# Subject.
#
# Your server-side code may not have access to all these parameters, or they
# might not be useful to you. All the Subject parameters are optional, except
# `platform`.
#
# @example Subject methods can be called from their associated Tracker
# # Creating the components explicitly
# emitter = SnowplowTracker::Emitter.new(endpoint: 'localhost')
# subject = SnowplowTracker::Subject.new
# tracker = SnowplowTracker::Tracker.new(emitters: emitter, subject: subject)
#
# # These lines are equivalent
# subject.set_user_id('12345')
# tracker.set_user_id('12345')
#
# # This would also be equivalent
# emitter = SnowplowTracker::Emitter.new(endpoint: 'localhost')
# subject = SnowplowTracker::Subject.new
# subject.set_user_id('12345')
# tracker = SnowplowTracker::Tracker.new(emitters: emitter, subject: subject)
#
# @example Adding properties to the auto-generated Tracker-associated Subject
# # Creating the components
# emitter = SnowplowTracker::Emitter.new(endpoint: 'localhost')
# tracker = SnowplowTracker::Tracker.new(emitters: emitter)
#
# # Set Subject parameters via the Tracker
# tracker.set_user_id('12345')
#
# Since many of the Subject parameters describe the user, different Subject
# properties may often be desired for each event, if there are multiple users.
# This can be achieved in one of three ways:
#
# 1. the properties of the Tracker-associated Subject can be overriden by the
# properties of an event-specific Subject. A Subject can be added to any
# Tracker `#track_x_event` method call, as one of the arguments. Remember to
# set the platform for the event Subject if you're not using `srv`.
# 2. the Tracker-associated Subject can be swapped for another Subject, using
# the Tracker method {Tracker#set_subject}.
# 3. the properties of the Tracker-associated Subject can be changed before
# every `#track_x_event`, by calling the Subject methods via the Tracker.
#
# @see Tracker#set_subject
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol
# the Snowplow Tracker Protocol
# @see
# https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/enriching-your-events/
# the Snowplow docs page about adding context and other extra data to events
# @api public
#
# @note All the Subject instance methods return the Subject object, allowing
# method chaining, e.g.
# `SnowplowTracker::Subject.new.set_timezone('Europe/London').set_user_id('12345')`
class Subject
# @private
DEFAULT_PLATFORM = 'srv'
# @api public
#
# | `p` value | Platform |
# | ---- | ---- |
# | app | General App |
# | cnsl | Games Console |
# | iot | Internet of Things |
# | mob | Mobile/Tablet |
# | pc | Desktop/Laptop/Netbook |
# | srv [DEFAULT] | Server-side App |
# | tv | Connected TV |
# | web | Web (including Mobile Web) |
SUPPORTED_PLATFORMS = %w[app cnsl iot mob pc srv tv web]
# Access the Subject parameters
# @example
# SnowplowTracker::Subject.new.set_user_id('12345').details
# => {"p"=>"srv", "uid"=>"12345"}
# @api public
attr_reader :details
# @api public
def initialize
@details = { 'p' => DEFAULT_PLATFORM }
end
# Set the platform to one of the supported platform values.
# @note Value is sent in the event as `p` (raw event) or `platform` (processed event).
# @see Subject::SUPPORTED_PLATFORMS
# @param [String] platform a valid platform choice
# @example
# subject.set_platform('app')
# @return self
# @api public
def set_platform(platform)
raise "#{platform} is not a supported platform" unless SUPPORTED_PLATFORMS.include?(platform)
@details['p'] = platform
self
end
# Set the unique business-defined user ID for a user.
# @note Value is sent in the event as `uid` (raw event) or `user_id` (processed event).
# For example, an email address.
# @example Example user IDs
# # an email address
# janet.bloggs@email.com
#
# # a username
# janetabloggs2021
#
# @param [String] user_id a unique user ID
# @return self
# @api public
def set_user_id(user_id)
@details['uid'] = user_id
self
end
# Set a business-defined fingerprint for a user.
# @note Value is sent in the event as `fp` (raw event) or `user_fingerprint` (processed event).
# @param [Num] fingerprint a user fingerprint
# @return self
# @example
# subject.set_fingerprint(4048966212)
# @api public
def set_fingerprint(fingerprint)
@details['fp'] = fingerprint
self
end
# Set the device screen resolution.
# @note Value is sent in the event as `res` (raw event) or `dvce_screenheight` and `dvce_screenwidth` (processed event).
# @param [Integer] width the screen width, in pixels (must be a positive integer)
# @param [Integer] height the screen height, in pixels (must be a positive integer)
# @return self
# @example
# subject.set_screen_resolution(width: 2880, height: 1800)
# @api public
def set_screen_resolution(width:, height:)
@details['res'] = "#{width}x#{height}"
self
end
# Set the dimensions of the current viewport.
# @note Value is sent in the event as `vp` (raw event) or `br_viewwidth` and `br_viewheight` (processed event).
# @param [Integer] width the viewport width, in pixels (must be a positive integer)
# @param [Integer] height the viewport height, in pixels (must be a positive integer)
# @return self
# @example
# subject.set_viewport(width: 1440, height: 762)
# @api public
def set_viewport(width:, height:)
@details['vp'] = "#{width}x#{height}"
self
end
# Set the color depth of the browser, in bits per pixel.
# @note Value is sent in the event as `cd` (raw event) or `br_colordepth` (processed event).
# @param [Num] depth the colour depth
# @example
# subject.set_color_depth(24)
# @return self
# @api public
def set_color_depth(depth)
@details['cd'] = depth
self
end
# Set the timezone to that of the user's OS.
# @note Value is sent in the event as `tz` (raw event) or `os_timezone` (processed event).
# @example
# subject.set_timezone('Africa/Lagos')
# @param [String] timezone the timezone
# @return self
# @api public
def set_timezone(timezone)
@details['tz'] = timezone
self
end
# Set the language.
# @note Value is sent in the event as `lang` (raw event) or `br_lang` (processed event).
# @example Setting the language to Spanish
# subject.set_lang('es')
# @param [String] lang the language being used on the device
# @return self
# @api public
def set_lang(lang)
@details['lang'] = lang
self
end
# Set the domain user ID.
# @note Value is sent in the event as `duid` (raw event) or `domain_userid` (processed event).
# @see Subject#set_network_user_id
# @see Subject#set_domain_session_id
# @see Subject#set_domain_session_idx
# @see https://github.com/simplybusiness/snowplow_ruby_duid/ snowplow_ruby_duid, a third party gem
# @see https://github.com/snowplow-incubator/snowplow-ruby-tracker-examples
# Ruby tracker example Rails app
# @example
# subject.set_domain_user_id('aeb1691c5a0ee5a6')
# @param [String] duid the unique domain user ID
# @return self
# @api public
#
# The `domain_userid` is a client-side unique user ID, which is set by the
# browser-based JavaScript tracker, and stored in a first party cookie
# (cookie name: `_sp_id`). For stitching together client-side and
# server-side events originating from the same user, the domain user ID can
# be extracted from the cookie and set using this method. A third party gem,
# [snowplow_ruby_duid](https://github.com/simplybusiness/snowplow_ruby_duid/),
# has been created to help with this.
#
# @example Ruby on Rails: getting the domain_user_id from the cookie
# # Assuming the Snowplow JavaScript has also been incorporated
# # cookies are accessible only within a Controller
# def snowplow_domain_userid
# sp_cookie = cookies.find { |key, _value| key =~ /^_sp_id/ }
# sp_cookie.last.split(".").first if sp_cookie.present?
# end
#
def set_domain_user_id(duid)
@details['duid'] = duid
self
end
# Set the domain session ID.
# @note Value is sent in the event as `sid` (raw event) or `domain_sessionid` (processed event).
# @see Subject#set_network_user_id
# @see Subject#set_domain_user_id
# @see Subject#set_domain_session_idx
# @example
# subject.set_domain_session_id('9c65e7f3-8e8e-470d-b243-910b5b300da0')
# @param [String] sid the unique domain session ID
# @return self
# @api public
#
# The `domain_sessionid` is a client-side unique ID for a user's current
# session. It is set by the browser-based JavaScript trackers, and stored in
# a first party cookie (cookie name: `_sp_id`), along with other parameters
# such as `domain_userid`. For stitching together client-side and
# server-side events originating from the same user and session, the domain
# session ID can be extracted from the cookie and set using this method.
def set_domain_session_id(sid)
@details['sid'] = sid
self
end
# Set the domain session index.
# @note Value is sent in the event as `vid` (raw event) or `domain_sessionidx` (processed event).
# @see Subject#set_network_user_id
# @see Subject#set_domain_user_id
# @see Subject#set_domain_session_id
# @example
# subject.set_domain_session_idx(3)
# @param [Num] vid the number of sessions
# @return self
# @api public
#
# The `domain_sessionidx` is a client-side property that records how many
# visits (unique `domain_sessionid`s) a user (a unique `domain_userid`) has
# made to the site. It is stored in the first party cookie set by the
# JavaScript tracker, along with other parameters such as `domain_userid`.
# For stitching together client-side and server-side events originating from
# the same user and session, the domain session index can be extracted from
# the cookie and set using this method.
def set_domain_session_idx(vid)
@details['vid'] = vid
self
end
# Set the user's IP address.
# @note Value is sent in the event as `ip` (raw event) or `user_ipaddress` (processed event).
# @param [String] ip the IP address
# @return self
# @example
# subject.set_ip_address('37.157.33.178')
# @api public
def set_ip_address(ip)
@details['ip'] = ip
self
end
# Set the browser user agent.
# @note Value is sent in the event as `ua` (raw event) or `useragent` (processed event).
# @example
# subject.set_useragent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:92.0) Gecko/20100101 Firefox/92.0')
# @param [String] useragent the user agent string
# @return self
# @api public
def set_useragent(useragent)
@details['ua'] = useragent
self
end
# Set the network user ID.
# @note Value is sent in the event as `tnuid` (raw event) and `network_userid` (processed event).
# @see Subject#set_domain_user_id
#
# The network user ID is, like the `domain_userid`, a cookie-based unique
# user ID. It is stored in a third party cookie set by the event collector,
# hence the name "network" as it is set at a network level. It is the
# server-side user identifier. The raw event does not contain a `nuid`
# value; the `network_userid` property is added when the event is processed.
#
# The default behaviour is for the collector to provide a new cookie/network
# user ID for each event it receives. This method provides the ability to
# override the collector cookie's value with your own generated ID.
#
# Domain user IDs set on the Subject in this way are sent as `tnuid` in the
# raw event.
#
# @example
# subject.set_network_user_id('ecdff4d0-9175-40ac-a8bb-325c49733607')
# @param [String] nuid the network user ID
# @return self
# @api public
def set_network_user_id(nuid)
@details['tnuid'] = nuid
self
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker/page.rb 0000644 0001751 0001751 00000004673 14400614605 021430 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
module SnowplowTracker
# If the Ruby tracker is incorporated into a website server, the events
# tracked will describe user activity on specific webpages. Knowing on which
# page an event occurred can be very valuable.
#
# Add page URL, page title and referrer URL to any event by adding a Page
# object to any {Tracker} `#track_x_event` method call.
#
# Page parameters are saved into the tracked event as part of the 'atomic'
# event properties, which have their own column in the eventual events table.
# For example, a Page's `page_url` parameter will be sent as `url` in the
# raw event payload, ending up in the `page_url` column.
#
#
# @note For {Tracker#track_page_view}, properties set in the Page object will
# override those properties given as arguments.
class Page
# @return [Hash] the stored page properties
attr_reader :details
# Create a Page object for attaching page properties to events.
#
# Page properties will directly populate the event's `page_url`, `page_title` and `referrer` parameters.
#
# @example Creating a Page
# SnowplowTracker::Page.new(page_url: 'http://www.example.com/second-page',
# page_title: 'Example title',
# referrer: 'http://www.example.com/first-page')
#
# @param page_url [String] the page URL
# @param page_title [String] the title of the page
# @param referrer [String] the URL of the previous page
def initialize(page_url: nil, page_title: nil, referrer: nil)
@details = { 'url' => page_url,
'page' => page_title,
'refr' => referrer }
end
end
end
snowplow-tracker-0.8.0/lib/snowplow-tracker.rb 0000644 0001751 0001751 00000002170 14400614605 020502 0 ustar pravi pravi # Copyright (c) 2013-2021 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:: Snowplow Analytics Ltd
# Copyright:: Copyright (c) 2013-2021 Snowplow Analytics Ltd
# License:: Apache License Version 2.0
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/page.rb'
require 'snowplow-tracker/emitters.rb'
require 'snowplow-tracker/timestamp.rb'
require 'snowplow-tracker/tracker.rb'
snowplow-tracker-0.8.0/README.md 0000644 0001751 0001751 00000011723 14400614605 015351 0 ustar pravi pravi # Ruby Analytics for Snowplow
[![early-release]][tracker-classification]
[](https://badge.fury.io/rb/snowplow-tracker)
[![Build Status][gh-actions-image]][gh-actions]
[](https://codeclimate.com/github/snowplow/snowplow-ruby-tracker)
[](https://coveralls.io/r/snowplow/snowplow-ruby-tracker)
[![License][license-image]][license]
## Overview
Add analytics to your **[Ruby][ruby]** and **[Ruby on Rails][rails]** apps and **[gems][rubygems]** with the **[Snowplow][snowplow]** event tracker for **[Ruby][ruby]**.
Snowplow is a scalable open-source platform for rich, high quality, low-latency data collection. It is designed to collect high quality, complete behavioral data for enterprise business.
**To find out more, please check out the [Snowplow website][snowplow] and our [documentation][docs].**
## Quickstart
Add this gem to your Gemfile. It is compatible with Ruby versions 2.1 to 3.0+.
```ruby
gem "snowplow-tracker", "~> 0.8.0"
```
See our [demo app][demoapp] for an example of implementing the Ruby tracker in a Rails app.
## Find out more
| Snowplow Docs | API Docs | Contributing |
| ------------------------------ | ----------------------- | ----------------------------------- |
| ![i1][techdocs-image] | ![i1][techdocs-image] | ![i4][contributing-image] |
| **[Snowplow Docs][techdocs]** | **[API Docs][apidocs]** | **[Contributing](Contributing.md)** |
## Maintainer Quickstart
Clone this repo and navigate into the cloned folder. To run the tests locally, you will need [Docker][docker] installed.
```bash
docker build . -t ruby-tracker
docker run -v "$(pwd)":"/code" ruby-tracker
```
The `-v` flag for `docker run` creates a bind mount for the project directory. This means that changes to the files will be automatically applied within the Docker image. However, if you modify the `Gemfile` or `snowplow-tracker.gemspec` files, the image must be rebuilt.
Alternatively, test directly by installing Ruby 2.1+ and [Bundler][bundler]. Then run:
```bash
bundle install
rspec
```
To generate documentation using YARD, make sure the YARD and redcarpet gems are installed locally. Then run:
```bash
yard doc
```
## Contributing
Feedback and contributions are welcome - if you have identified a bug, please log an issue on this repo. For all other feedback, discussion or questions please open a thread on our [Discourse forum][discourse].
## Copyright and license
The Snowplow Ruby Tracker is copyright 2013-2021 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]: https://img.shields.io/badge/license-Apache--2-blue.svg?style=flat
[license]: https://www.apache.org/licenses/LICENSE-2.0
[gh-actions]: https://github.com/snowplow/snowplow-ruby-tracker/actions
[gh-actions-image]: https://github.com/snowplow/snowplow-ruby-tracker/workflows/Test/badge.svg
[tracker-classification]: https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/tracker-maintenance-classification/
[early-release]: https://img.shields.io/static/v1?style=flat&label=Snowplow&message=Early%20Release&color=014477&labelColor=9ba0aa&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAeFBMVEVMaXGXANeYANeXANZbAJmXANeUANSQAM+XANeMAMpaAJhZAJeZANiXANaXANaOAM2WANVnAKWXANZ9ALtmAKVaAJmXANZaAJlXAJZdAJxaAJlZAJdbAJlbAJmQAM+UANKZANhhAJ+EAL+BAL9oAKZnAKVjAKF1ALNBd8J1AAAAKHRSTlMAa1hWXyteBTQJIEwRgUh2JjJon21wcBgNfmc+JlOBQjwezWF2l5dXzkW3/wAAAHpJREFUeNokhQOCA1EAxTL85hi7dXv/E5YPCYBq5DeN4pcqV1XbtW/xTVMIMAZE0cBHEaZhBmIQwCFofeprPUHqjmD/+7peztd62dWQRkvrQayXkn01f/gWp2CrxfjY7rcZ5V7DEMDQgmEozFpZqLUYDsNwOqbnMLwPAJEwCopZxKttAAAAAElFTkSuQmCC
[ruby]: https://www.ruby-lang.org/en/
[rails]: https://rubyonrails.org/
[rubygems]: https://rubygems.org/
[docker]: https://www.docker.com/
[bundler]: https://bundler.io/
[snowplow]: https://snowplowanalytics.com
[docs]: https://docs.snowplowanalytics.com/
[demoapp]: https://github.com/snowplow-incubator/snowplow-ruby-tracker-examples
[discourse]: https://discourse.snowplowanalytics.com
[techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png
[contributing-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/contributing.png
[techdocs]: https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/
[apidocs]: https://snowplow.github.io/snowplow-ruby-tracker/
snowplow-tracker-0.8.0/snowplow-tracker.gemspec 0000644 0001751 0001751 00000003465 14400614605 020764 0 ustar pravi pravi #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: snowplow-tracker 0.8.0 ruby lib
Gem::Specification.new do |s|
s.name = "snowplow-tracker".freeze
s.version = "0.8.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["Snowplow Analytics Ltd".freeze]
s.date = "2021-10-29"
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/emitters.rb".freeze, "lib/snowplow-tracker/page.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-2.0".freeze]
s.required_ruby_version = Gem::Requirement.new(">= 2.1".freeze)
s.rubygems_version = "3.3.15".freeze
s.summary = "Ruby Analytics for Snowplow".freeze
if s.respond_to? :specification_version then
s.specification_version = 4
end
if s.respond_to? :add_runtime_dependency then
s.add_development_dependency(%q.freeze, ["~> 3.10"])
s.add_development_dependency(%q.freeze, ["~> 3.14"])
else
s.add_dependency(%q.freeze, ["~> 3.10"])
s.add_dependency(%q.freeze, ["~> 3.14"])
end
end