From 298e12f0e1eee173e26510ec25d1b4746ac9d862 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Fri, 30 Aug 2013 18:56:54 -0700 Subject: [PATCH 001/322] adding 1.8.7 support Ruby 1.8.7 requires adding json as a dependency for use with faraday_middleware. Added the versions to travis as well. --- .travis.yml | 12 ++++++++++-- analytics-ruby.gemspec | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4916d92..02ed87d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,12 @@ language: ruby + rvm: - - "1.9.3" - - "1.9.2" + - rbx-18mode + - rbx-19mode + - rbx-20mode + - jruby-18mode + - jruby-19mode + - 1.8.7 + - 1.9.2 + - 1.9.3 + - 2.0.0 \ No newline at end of file diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 9fcf5af..4b78df3 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -18,6 +18,9 @@ Gem::Specification.new do |spec| spec.add_dependency 'faraday_middleware', ['>= 0.8', '< 0.10'] spec.add_dependency 'multi_json', ['~> 1.0'] + # Ruby 1.8 requires json for faraday_middleware + spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" + spec.add_development_dependency('rake') spec.add_development_dependency('rspec') end From f7b0ca28e6f910a201b191a8078703b043c9f450 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Fri, 30 Aug 2013 19:03:14 -0700 Subject: [PATCH 002/322] release: 0.4.0 Adding support and tets for 1.8.7 --- History.md | 60 +++++++++++++++++++---------------- lib/analytics-ruby/version.rb | 2 +- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/History.md b/History.md index 08aebbe..9d3fd5d 100644 --- a/History.md +++ b/History.md @@ -1,60 +1,64 @@ -0.3.4 / 2013-8-26 -=========== +0.4.0 / 2013-08-30 +================== +* Adding support and tests for 1.8.7 + +0.3.4 / 2013-08-26 +================== * Pass `Time` values as iso8601 timestamp strings -0.3.3 / 2013-8-2 -=========== +0.3.3 / 2013-08-02 +================== * Allow init/track/identify/alias to accept strings as keys. by [@shipstar](https://github.com/shipstar) -0.3.2 / 2013-5-28 -=========== +0.3.2 / 2013-05-28 +================== * Adding faraday timeout by [@yanchenyun](https://github.com/yangchenyun) -0.3.1 / 2013-4-29 -=========== +0.3.1 / 2013-04-29 +================== * Adding check for properties to be a Hash -0.3.0 / 2013-4-5 -=========== +0.3.0 / 2013-04-05 +================== * Adding alias call -0.2.0 / 2013-3-21 -=========== +0.2.0 / 2013-03-21 +================== * Adding flush method -0.1.4 / 2013-3-19 -=========== +0.1.4 / 2013-03-19 +================== * Adding ClassMethods for more extensibility by [arronmabrey](https://github.com/arronmabrey) -0.1.3 / 2013-3-19 -=========== +0.1.3 / 2013-03-19 +================== * Fixing user_id.to_s semantics, reported by [arronmabrey](https://github.com/arronmabrey) * Reduced faraday requirements by [arronmabrey](https://github.com/arronmabrey) -0.1.2 / 2013-3-11 -=========== +0.1.2 / 2013-03-11 +================== * Fixing thrown exception on non-initialized tracks thanks to [sbellity](https://github.com/sbellity) -0.1.1 / 2013-2-11 -=========== +0.1.1 / 2013-02-11 +================== * Updating dependencies * Adding actual support for MultiJson 1.0 -0.1.0 / 2013-1-22 -=========== +0.1.0 / 2013-01-22 +================== * Updated docs to point at segment.io -0.0.5 / 2013-1-21 -=========== +0.0.5 / 2013-01-21 +================== * Renaming of all the files for proper bundling usage -0.0.4 / 2013-1-17 -=========== +0.0.4 / 2013-01-17 +================== * Updated readme and install instruction courtesy of [@zeke](https://github.com/zeke) * Removed typhoeus and reverted to default adapter * Removing session_id in favor of a single user_id -0.0.3 / 2013-1-16 -=========== +0.0.3 / 2013-01-16 +================== * Rakefile and renaming courtesy of [@kiennt](https://github.com/kiennt) * Updated tests with mocks \ No newline at end of file diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 18dcf88..77f6bf8 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.3.4' + VERSION = '0.4.0' end \ No newline at end of file From 4699252aaa17b91bbab3a29e02932411b444af44 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Thu, 3 Oct 2013 19:07:13 -0700 Subject: [PATCH 003/322] namespace: renaming Analytics -> AnalyticsRuby Fixes #15, and allows the developer to choose the namespace. Our library will only use the namespace we _actually_ own, and we've updated the docs to alias to Analytics manually if the user so desires. --- History.md | 5 +++++ lib/analytics-ruby.rb | 5 +---- spec/client_spec.rb | 32 ++++++++++++++++---------------- spec/consumer_spec.rb | 24 ++++++++++++------------ spec/module_spec.rb | 32 ++++++++++++++++---------------- spec/spec_helper.rb | 2 +- 6 files changed, 51 insertions(+), 49 deletions(-) diff --git a/History.md b/History.md index 9d3fd5d..243fcd6 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +0.5.0 / 2013-10-03 +================== + +* Removing global Analytics alias in favor of adding it to our config + 0.4.0 / 2013-08-30 ================== * Adding support and tests for 1.8.7 diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb index a03678f..4e6e19a 100644 --- a/lib/analytics-ruby.rb +++ b/lib/analytics-ruby.rb @@ -29,7 +29,4 @@ def flush end end extend ClassMethods -end - -# Alias for AnalyticsRuby -Analytics = AnalyticsRuby +end \ No newline at end of file diff --git a/spec/client_spec.rb b/spec/client_spec.rb index b1c00b6..6028497 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -2,27 +2,27 @@ require 'spec_helper' -describe Analytics::Client do +describe AnalyticsRuby::Client do describe '#init' do it 'should error if no secret is supplied' do - expect { Analytics::Client.new }.to raise_error(RuntimeError) + expect { AnalyticsRuby::Client.new }.to raise_error(RuntimeError) end it 'should not error if a secret is supplied' do - Analytics::Client.new :secret => AnalyticsHelpers::SECRET + AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET end it 'should not error if a secret is supplied as a string' do - Analytics::Client.new 'secret' => AnalyticsHelpers::SECRET + AnalyticsRuby::Client.new 'secret' => AnalyticsRubyHelpers::SECRET end end describe '#track' do before(:all) do - @client = Analytics::Client.new :secret => AnalyticsHelpers::SECRET + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end @@ -46,12 +46,12 @@ end it 'should not error with the required options' do - @client.track AnalyticsHelpers::Queued::TRACK + @client.track AnalyticsRubyHelpers::Queued::TRACK @queue.pop end it 'should not error when given string keys' do - @client.track Util.stringify_keys(AnalyticsHelpers::Queued::TRACK) + @client.track Util.stringify_keys(AnalyticsRubyHelpers::Queued::TRACK) @queue.pop end @@ -74,7 +74,7 @@ describe '#identify' do before(:all) do - @client = Analytics::Client.new :secret => AnalyticsHelpers::SECRET + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end @@ -84,12 +84,12 @@ end it 'should not error with the required options' do - @client.identify AnalyticsHelpers::Queued::IDENTIFY + @client.identify AnalyticsRubyHelpers::Queued::IDENTIFY @queue.pop end it 'should not error with the required options as strings' do - @client.identify Util.stringify_keys(AnalyticsHelpers::Queued::IDENTIFY) + @client.identify Util.stringify_keys(AnalyticsRubyHelpers::Queued::IDENTIFY) @queue.pop end @@ -109,7 +109,7 @@ describe '#alias' do before :all do - @client = Analytics::Client.new :secret => AnalyticsHelpers::SECRET + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET end it 'should error without from' do @@ -121,22 +121,22 @@ end it 'should not error with the required options' do - @client.alias AnalyticsHelpers::ALIAS + @client.alias AnalyticsRubyHelpers::ALIAS end it 'should not error with the required options as strings' do - @client.alias Util.stringify_keys(AnalyticsHelpers::ALIAS) + @client.alias Util.stringify_keys(AnalyticsRubyHelpers::ALIAS) end end describe '#flush' do before(:all) do - @client = Analytics::Client.new :secret => AnalyticsHelpers::SECRET + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET end it 'should wait for the queue to finish on a flush' do - @client.identify AnalyticsHelpers::Queued::IDENTIFY - @client.track AnalyticsHelpers::Queued::TRACK + @client.identify AnalyticsRubyHelpers::Queued::IDENTIFY + @client.track AnalyticsRubyHelpers::Queued::TRACK @client.flush @client.queued_messages.should == 0 end diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb index b7cf7b1..e533b7a 100644 --- a/spec/consumer_spec.rb +++ b/spec/consumer_spec.rb @@ -2,12 +2,12 @@ require 'thread' require 'spec_helper' -describe Analytics::Consumer do +describe AnalyticsRuby::Consumer do describe "#init" do it 'accepts string keys' do queue = Queue.new - consumer = Analytics::Consumer.new(queue, 'secret', 'batch_size' => 100) + consumer = AnalyticsRuby::Consumer.new(queue, 'secret', 'batch_size' => 100) consumer.instance_variable_get(:@batch_size).should == 100 end end @@ -20,7 +20,7 @@ queue = Queue.new queue << {} - consumer = Analytics::Consumer.new(queue, 'secret') + consumer = AnalyticsRuby::Consumer.new(queue, 'secret') consumer.flush queue.should be_empty @@ -30,8 +30,8 @@ it 'should execute the error handler if the request is invalid' do - Analytics::Request.any_instance.stub(:post).and_return( - Analytics::Response.new(400, "Some error")) + AnalyticsRuby::Request.any_instance.stub(:post).and_return( + AnalyticsRuby::Response.new(400, "Some error")) on_error = Proc.new do |status, error| puts "#{status}, #{error}" @@ -41,10 +41,10 @@ queue = Queue.new queue << {} - consumer = Analytics::Consumer.new queue, 'secret', :on_error => on_error + consumer = AnalyticsRuby::Consumer.new queue, 'secret', :on_error => on_error consumer.flush - Analytics::Request::any_instance.unstub(:post) + AnalyticsRuby::Request::any_instance.unstub(:post) queue.should be_empty end @@ -58,8 +58,8 @@ on_error.should_receive(:call).at_most(0).times queue = Queue.new - queue << AnalyticsHelpers::Requested::TRACK - consumer = Analytics::Consumer.new queue, 'testsecret', :on_error => on_error + queue << AnalyticsRubyHelpers::Requested::TRACK + consumer = AnalyticsRuby::Consumer.new queue, 'testsecret', :on_error => on_error consumer.flush queue.should be_empty @@ -71,7 +71,7 @@ it 'should not return true if there isn\'t a current batch' do queue = Queue.new - consumer = Analytics::Consumer.new(queue, 'testsecret') + consumer = AnalyticsRuby::Consumer.new(queue, 'testsecret') consumer.is_requesting?.should == false end @@ -79,8 +79,8 @@ it 'should return true if there is a current batch' do queue = Queue.new - queue << AnalyticsHelpers::Requested::TRACK - consumer = Analytics::Consumer.new(queue, 'testsecret') + queue << AnalyticsRubyHelpers::Requested::TRACK + consumer = AnalyticsRuby::Consumer.new(queue, 'testsecret') Thread.new { consumer.flush diff --git a/spec/module_spec.rb b/spec/module_spec.rb index 76a6855..900b2ce 100644 --- a/spec/module_spec.rb +++ b/spec/module_spec.rb @@ -1,45 +1,45 @@ require 'analytics-ruby' require 'spec_helper' -describe Analytics do +describe AnalyticsRuby do describe '#not-initialized' do it 'should ignore calls to track if not initialized' do - expect { Analytics.track({}) }.not_to raise_error + expect { AnalyticsRuby.track({}) }.not_to raise_error end it 'should return false on track if not initialized' do - Analytics.track({}).should == false + AnalyticsRuby.track({}).should == false end it 'should ignore calls to identify if not initialized' do - expect { Analytics.identify({}) }.not_to raise_error + expect { AnalyticsRuby.identify({}) }.not_to raise_error end it 'should return false on identify if not initialized' do - Analytics.identify({}).should == false + AnalyticsRuby.identify({}).should == false end end describe '#init' do it 'should successfully init' do - Analytics.init :secret => AnalyticsHelpers::SECRET + AnalyticsRuby.init :secret => AnalyticsRubyHelpers::SECRET end end describe '#track' do it 'should error without an event' do - expect { Analytics.track :user_id => 'user' }.to raise_error(ArgumentError) + expect { AnalyticsRuby.track :user_id => 'user' }.to raise_error(ArgumentError) end it 'should error without a user_id' do - expect { Analytics.track :event => 'Event' }.to raise_error(ArgumentError) + expect { AnalyticsRuby.track :event => 'Event' }.to raise_error(ArgumentError) end it 'should not error with the required options' do - Analytics.track AnalyticsHelpers::Queued::TRACK + AnalyticsRuby.track AnalyticsRubyHelpers::Queued::TRACK sleep(1) end end @@ -47,26 +47,26 @@ describe '#identify' do it 'should error without a user_id' do - expect { Analytics.identify :traits => {} }.to raise_error(ArgumentError) + expect { AnalyticsRuby.identify :traits => {} }.to raise_error(ArgumentError) end it 'should not error with the required options' do - Analytics.identify AnalyticsHelpers::Queued::IDENTIFY + AnalyticsRuby.identify AnalyticsRubyHelpers::Queued::IDENTIFY sleep(1) end end describe '#alias' do it 'should error without from' do - expect { Analytics.alias :to => 1234 }.to raise_error(ArgumentError) + expect { AnalyticsRuby.alias :to => 1234 }.to raise_error(ArgumentError) end it 'should error without to' do - expect { Analytics.alias :from => 1234 }.to raise_error(ArgumentError) + expect { AnalyticsRuby.alias :from => 1234 }.to raise_error(ArgumentError) end it 'should not error with the required options' do - Analytics.alias AnalyticsHelpers::ALIAS + AnalyticsRuby.alias AnalyticsRubyHelpers::ALIAS sleep(1) end end @@ -74,8 +74,8 @@ describe '#flush' do it 'should flush without error' do - Analytics.identify AnalyticsHelpers::Queued::IDENTIFY - Analytics.flush + AnalyticsRuby.identify AnalyticsRubyHelpers::Queued::IDENTIFY + AnalyticsRuby.flush end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f02058f..d3cd1aa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ -module AnalyticsHelpers +module AnalyticsRubyHelpers SECRET = 'testsecret' From d63c2871b3db1c85d68973e68b859b74a22697b8 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Thu, 3 Oct 2013 19:09:15 -0700 Subject: [PATCH 004/322] release: 0.5.0 4699252 namespace: removing Analytics -> AnalyticsRuby --- lib/analytics-ruby/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 77f6bf8..610a05a 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.4.0' + VERSION = '0.5.0' end \ No newline at end of file From a41c1dcdda201a4b272058fcbb04117822c0c677 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Fri, 4 Oct 2013 16:59:49 -0700 Subject: [PATCH 005/322] spec: adding license to spec, closes #20 --- analytics-ruby.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 4b78df3..3c84d59 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -13,6 +13,7 @@ Gem::Specification.new do |spec| spec.authors = ['Segment.io'] spec.email = 'friends@segment.io' spec.homepage = 'https://github.com/segmentio/analytics-ruby' + spec.license = 'MIT' spec.add_dependency 'faraday', ['>= 0.8', '< 0.10'] spec.add_dependency 'faraday_middleware', ['>= 0.8', '< 0.10'] From db98fbed68ccc9d814ef9a3b46927aa71a154751 Mon Sep 17 00:00:00 2001 From: Bitdeli Chef Date: Wed, 30 Oct 2013 01:53:17 +0000 Subject: [PATCH 006/322] Add a Bitdeli badge to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 834a965..18eddf6 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/segmentio/analytics-ruby/trend.png)](https://bitdeli.com/free "Bitdeli Badge") + From 2705c9449d38905638fb11172692d82c0d340c15 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Fri, 22 Nov 2013 15:52:04 -0800 Subject: [PATCH 007/322] adding retries for connection hangups Instead of erroring on a connection hangup, the request will attempt to retry. --- lib/analytics-ruby/request.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/analytics-ruby/request.rb b/lib/analytics-ruby/request.rb index 36adf24..c08c5cf 100644 --- a/lib/analytics-ruby/request.rb +++ b/lib/analytics-ruby/request.rb @@ -31,6 +31,7 @@ def initialize(options = {}) def post(secret, batch) status, error = nil, nil + remaining_retries = 3 begin res = @conn.post do |req| @@ -45,6 +46,7 @@ def post(secret, batch) rescue Exception => err status = -1 error = "Connection error: #{err}" + retry unless (remaining_retries -=1).zero? end AnalyticsRuby::Response.new status, error From 1663159626216491199fc0de3293adb9acd00f87 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Fri, 22 Nov 2013 15:53:29 -0800 Subject: [PATCH 008/322] 0.5.1 adding retries for connection hangups --- History.md | 4 ++++ lib/analytics-ruby/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 243fcd6..19091fc 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +0.5.1 / 2013-11-22 +================== +* adding retries for connection hangups + 0.5.0 / 2013-10-03 ================== diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 610a05a..231ff50 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.5.0' + VERSION = '0.5.1' end \ No newline at end of file From fd1886ae3b695e67baab3eeb7339d191643a69b5 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Mon, 2 Dec 2013 15:32:53 -0800 Subject: [PATCH 009/322] request: adding sleep, backoff and upping the retry count This commit ensures that connection errors will retry after 30 seconds several times in a row --- lib/analytics-ruby/defaults.rb | 2 ++ lib/analytics-ruby/request.rb | 10 ++++++++-- spec/consumer_spec.rb | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/analytics-ruby/defaults.rb b/lib/analytics-ruby/defaults.rb index 4eb6b72..4929364 100644 --- a/lib/analytics-ruby/defaults.rb +++ b/lib/analytics-ruby/defaults.rb @@ -7,6 +7,8 @@ module Request PATH = '/v1/import' unless defined? AnalyticsRuby::Defaults::Request::PATH SSL = { :verify => false } unless defined? AnalyticsRuby::Defaults::Request::SSL HEADERS = { :accept => 'application/json' } unless defined? AnalyticsRuby::Defaults::Request::HEADERS + RETRIES = 4 unless defined? AnalyticsRuby::Defaults::Request::RETRIES + BACKOFF = 30.0 unless defined? AnalyticsRuby::Defaults::Request::BACKOFF end module Queue diff --git a/lib/analytics-ruby/request.rb b/lib/analytics-ruby/request.rb index c08c5cf..01c3ba7 100644 --- a/lib/analytics-ruby/request.rb +++ b/lib/analytics-ruby/request.rb @@ -17,6 +17,8 @@ def initialize(options = {}) options[:ssl] ||= AnalyticsRuby::Defaults::Request::SSL options[:headers] ||= AnalyticsRuby::Defaults::Request::HEADERS @path = options[:path] || AnalyticsRuby::Defaults::Request::PATH + @retries = options[:retries] || AnalyticsRuby::Defaults::Request::RETRIES + @backoff = options[:backoff] || AnalyticsRuby::Defaults::Request::BACKOFF @conn = Faraday.new options do |faraday| faraday.request :json @@ -31,7 +33,8 @@ def initialize(options = {}) def post(secret, batch) status, error = nil, nil - remaining_retries = 3 + remaining_retries = @retries + backoff = @backoff begin res = @conn.post do |req| @@ -46,7 +49,10 @@ def post(secret, batch) rescue Exception => err status = -1 error = "Connection error: #{err}" - retry unless (remaining_retries -=1).zero? + unless (remaining_retries -=1).zero? + sleep(backoff) + retry + end end AnalyticsRuby::Response.new status, error diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb index e533b7a..3ae2e7c 100644 --- a/spec/consumer_spec.rb +++ b/spec/consumer_spec.rb @@ -14,6 +14,14 @@ describe '#flush' do + before :all do + AnalyticsRuby::Defaults::Request::BACKOFF = 0.1 + end + + after :all do + AnalyticsRuby::Defaults::Request::BACKOFF = 30.0 + end + it 'should not error if the endpoint is unreachable' do Faraday::Connection.any_instance.stub(:post).and_raise(Exception) From 9dee72768753eb4c4eb5ddc465cf82572e00ead2 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Mon, 2 Dec 2013 15:39:35 -0800 Subject: [PATCH 010/322] 0.5.2 fd1886a request: adding sleep, backoff and upping the retry count --- History.md | 4 ++++ Makefile | 3 +++ lib/analytics-ruby/version.rb | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 19091fc..a2314b7 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +0.5.2 / 2013-12-02 +================== +* adding `sleep` backoff between connection retries + 0.5.1 / 2013-11-22 ================== * adding retries for connection hangups diff --git a/Makefile b/Makefile index 6bd876a..dc6ade0 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,7 @@ test: rake spec +build: + gem build ./analytics-ruby.gemspec + .PHONY: test \ No newline at end of file diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 231ff50..3d6827a 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.5.1' + VERSION = '0.5.2' end \ No newline at end of file From 278572298f4020aadb898089e3fc323883bdc51d Mon Sep 17 00:00:00 2001 From: Kevin Menard Date: Mon, 23 Dec 2013 12:36:39 -0500 Subject: [PATCH 011/322] Allow the consumer thread to shut down so it won't remain live in hot deploy scenarios. Fixes #27: Memory leak on JRuby. --- lib/analytics-ruby/client.rb | 15 ++++++++++++++- lib/analytics-ruby/consumer.rb | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/analytics-ruby/client.rb b/lib/analytics-ruby/client.rb index bc25a3a..c16fb85 100644 --- a/lib/analytics-ruby/client.rb +++ b/lib/analytics-ruby/client.rb @@ -10,6 +10,10 @@ module AnalyticsRuby class Client + # Sub-class thread so we have a named thread (useful for debugging in Thread.list). + class ConsumerThread < Thread + end + # public: Creates a new client # # options - Hash @@ -27,7 +31,16 @@ def initialize(options = {}) check_secret @consumer = AnalyticsRuby::Consumer.new @queue, @secret, options - @thread = Thread.new { @consumer.run } + @thread = ConsumerThread.new { @consumer.run } + + at_exit do + # Let the consumer thread know it should exit. + @thread[:should_exit] = true + + # Push a flag value to the consumer queue in case it's blocked waiting for a value. This will allow it + # to continue its normal chain of processing, giving it a chance to exit. + @queue << nil + end end # public: Synchronously waits until the consumer has flushed the queue. diff --git a/lib/analytics-ruby/consumer.rb b/lib/analytics-ruby/consumer.rb index 68d26c0..4c276fd 100644 --- a/lib/analytics-ruby/consumer.rb +++ b/lib/analytics-ruby/consumer.rb @@ -34,7 +34,7 @@ def initialize(queue, secret, options = {}) # public: Continuously runs the loop to check for new events # def run - while true + until Thread.current[:should_exit] flush end end @@ -44,6 +44,7 @@ def run def flush # Block until we have something to send item = @queue.pop + return if item.nil? # Synchronize on additions to the current batch @mutex.synchronize { From 7bdda46ed05efed01f750f66b2dba4c3613a13ba Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Tue, 31 Dec 2013 12:28:08 -0800 Subject: [PATCH 012/322] 0.5.3 * allow the consumer thread to shut down so it won't remain live in hot deploy scenarios (nirvdrum) * clarifying changelog upgrade instructions --- History.md | 7 +++++-- lib/analytics-ruby/version.rb | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index a2314b7..0d025b1 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +0.5.3 / 2013-12-31 +================== +* Allow the consumer thread to shut down so it won't remain live in hot deploy scenarios. This fixes the jruby memory leak by [@nirvdrum](https://github.com/nirvdrum) + 0.5.2 / 2013-12-02 ================== * adding `sleep` backoff between connection retries @@ -8,8 +12,7 @@ 0.5.0 / 2013-10-03 ================== - -* Removing global Analytics alias in favor of adding it to our config +* Removing global Analytics alias in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. See the [setup docs for more info](https://segment.io/libraries/ruby) 0.4.0 / 2013-08-30 ================== diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 3d6827a..f59fbde 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.5.2' + VERSION = '0.5.3' end \ No newline at end of file From ecb253022823671b8d103e86cae9d9e09d95c8eb Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Tue, 31 Dec 2013 12:31:41 -0800 Subject: [PATCH 013/322] updating history --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 0d025b1..47b5272 100644 --- a/History.md +++ b/History.md @@ -12,7 +12,7 @@ 0.5.0 / 2013-10-03 ================== -* Removing global Analytics alias in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. See the [setup docs for more info](https://segment.io/libraries/ruby) +* Removing global Analytics alias in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. Otherwise you WILL NOT be sending analytics data. See the [setup docs for more info](https://segment.io/libraries/ruby) 0.4.0 / 2013-08-30 ================== From b15ab1dee80fc05c0ff2714a8a2bcb588005f0ec Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Tue, 31 Dec 2013 14:56:33 -0800 Subject: [PATCH 014/322] adding `requestId` to all requests going through the system --- lib/analytics-ruby/client.rb | 7 +++---- lib/analytics-ruby/util.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/analytics-ruby/client.rb b/lib/analytics-ruby/client.rb index c16fb85..0b1be17 100644 --- a/lib/analytics-ruby/client.rb +++ b/lib/analytics-ruby/client.rb @@ -138,11 +138,8 @@ def identify(options) # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def alias(options) - check_secret - Util.symbolize_keys! options - from = options[:from].to_s to = options[:to].to_s timestamp = options[:timestamp] || Time.new @@ -151,7 +148,6 @@ def alias(options) ensure_user from ensure_user to check_timestamp timestamp - add_context context enqueue({ @@ -176,6 +172,9 @@ def queued_messages # # returns Boolean of whether the item was added to the queue. def enqueue(action) + # add our request id for tracing purposes + action[:requestId] = Util.uid + queue_full = @queue.length >= @max_queue_size @queue << action unless queue_full diff --git a/lib/analytics-ruby/util.rb b/lib/analytics-ruby/util.rb index fb178aa..1be0494 100644 --- a/lib/analytics-ruby/util.rb +++ b/lib/analytics-ruby/util.rb @@ -1,16 +1,26 @@ module Util + + # public: Return a new hash with keys converted from strings to symbols + # def self.symbolize_keys(hash) hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo } end + # public: Convert hash keys from strings to symbols in place + # def self.symbolize_keys!(hash) hash.replace symbolize_keys hash end + # public: Return a new hash with keys as strings + # def self.stringify_keys(hash) hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo } end + # public: Returns a new hash with all the date values in the into iso8601 + # strings + # def self.isoify_dates(hash) hash.inject({}) { |memo, (k, v)| memo[k] = v.respond_to?(:iso8601) ? v.iso8601 : v @@ -18,7 +28,15 @@ def self.isoify_dates(hash) } end + # public: Converts all the date values in the into iso8601 strings in place + # def self.isoify_dates!(hash) hash.replace isoify_dates hash end + + # public: Returns a uid string + # + def self.uid + (0..16).to_a.map{|x| rand(16).to_s(16)}.join + end end \ No newline at end of file From 60981ee7273aa13e5613445e344a70c1042f4420 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Tue, 31 Dec 2013 15:08:07 -0800 Subject: [PATCH 015/322] 0.5.4 * adding `requestId`s to all requests --- History.md | 4 ++++ lib/analytics-ruby/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 47b5272..ff795f6 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +0.5.4 / 2013-12-31 +================== +* Add `requestId` fields to all requests for tracing. + 0.5.3 / 2013-12-31 ================== * Allow the consumer thread to shut down so it won't remain live in hot deploy scenarios. This fixes the jruby memory leak by [@nirvdrum](https://github.com/nirvdrum) diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index f59fbde..d15802e 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.5.3' + VERSION = '0.5.4' end \ No newline at end of file From b5e558444209d4798fc5a9947c65e3f1c99d5f41 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 19 Feb 2014 18:41:24 -0800 Subject: [PATCH 016/322] relaxing faraday dependency, fixes #31 --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 3c84d59..879e4b6 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| spec.homepage = 'https://github.com/segmentio/analytics-ruby' spec.license = 'MIT' - spec.add_dependency 'faraday', ['>= 0.8', '< 0.10'] + spec.add_dependency 'faraday', ['>= 0.8'] spec.add_dependency 'faraday_middleware', ['>= 0.8', '< 0.10'] spec.add_dependency 'multi_json', ['~> 1.0'] From 24cae46785c778576f30bebdf982775b9f4009de Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 19 Feb 2014 18:41:50 -0800 Subject: [PATCH 017/322] adding .group(), .page(), and .screen() calls To be released with the new API --- lib/analytics-ruby.rb | 15 ++++++ lib/analytics-ruby/client.rb | 102 +++++++++++++++++++++++++++++++++++ spec/client_spec.rb | 66 +++++++++++++++++++++++ spec/module_spec.rb | 45 ++++++++++++++++ spec/spec_helper.rb | 30 +++++++++-- 5 files changed, 254 insertions(+), 4 deletions(-) diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb index 4e6e19a..ce17440 100644 --- a/lib/analytics-ruby.rb +++ b/lib/analytics-ruby.rb @@ -23,6 +23,21 @@ def alias(options) @client.alias options end + def group(options) + return false unless @client + @client.group options + end + + def page(options) + return false unless @client + @client.page options + end + + def screen(options) + return false unless @client + @client.screen options + end + def flush return false unless @client @client.flush diff --git a/lib/analytics-ruby/client.rb b/lib/analytics-ruby/client.rb index 0b1be17..278fdd3 100644 --- a/lib/analytics-ruby/client.rb +++ b/lib/analytics-ruby/client.rb @@ -159,6 +159,108 @@ def alias(options) }) end + # public: Associates a user identity with a group. + # + # options - Hash + # :from - String of the id to alias from + # :to - String of the id to alias to + # :timestamp - Time of when the alias occured (optional) + # :context - Hash of context (optional) + def group(options) + check_secret + Util.symbolize_keys! options + group_id = options[:group_id].to_s + user_id = options[:user_id].to_s + traits = options[:traits] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash + + ensure_user group_id + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :groupId => group_id, + :userId => user_id, + :traits => traits, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'group' + }) + end + + # public: Records a page view + # + # options - Hash + # :user_id - String of the id to alias from + # :name - String name of the page + # :properties - Hash of page properties (optional) + # :timestamp - Time of when the pageview occured (optional) + # :context - Hash of context (optional) + def page(options) + check_secret + Util.symbolize_keys! options + user_id = options[:user_id].to_s + name = options[:name].to_s + properties = options[:properties] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.name must be a string' unless !name.empty? + fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + Util.isoify_dates! properties + + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :userId => user_id, + :name => name, + :properties => properties, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'page' + }) + end + # public: Records a screen view (for a mobile app) + # + # options - Hash + # :user_id - String of the id to alias from + # :name - String name of the screen + # :properties - Hash of screen properties (optional) + # :timestamp - Time of when the screen occured (optional) + # :context - Hash of context (optional) + def screen(options) + check_secret + Util.symbolize_keys! options + user_id = options[:user_id].to_s + name = options[:name].to_s + properties = options[:properties] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.name must be a string' if name.empty? + fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + Util.isoify_dates! properties + + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :userId => user_id, + :name => name, + :properties => properties, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'screen' + }) + end + # public: Returns the number of queued messages # # returns Fixnum of messages in the queue diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 6028497..733569e 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -129,6 +129,72 @@ end end + describe '#group' do + before :all do + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET + end + + it 'should error without group_id' do + expect { @client.group :user_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without user_id' do + expect { @client.group :group_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.group AnalyticsRubyHelpers::Queued::GROUP + end + + it 'should not error with the required options as strings' do + @client.group Util.stringify_keys(AnalyticsRubyHelpers::Queued::GROUP) + end + end + + describe '#page' do + before :all do + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET + end + + it 'should error without user_id' do + expect { @client.page :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { @client.page :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.page AnalyticsRubyHelpers::Queued::PAGE + end + + it 'should not error with the required options as strings' do + @client.page Util.stringify_keys(AnalyticsRubyHelpers::Queued::PAGE) + end + end + + describe '#screen' do + before :all do + @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET + end + + it 'should error without user_id' do + expect { @client.screen :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { A@client.screen :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.screen AnalyticsRubyHelpers::Queued::SCREEN + end + + it 'should not error with the required options as strings' do + @client.screen Util.stringify_keys(AnalyticsRubyHelpers::Queued::SCREEN) + end + end + describe '#flush' do before(:all) do @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET diff --git a/spec/module_spec.rb b/spec/module_spec.rb index 900b2ce..b3e6681 100644 --- a/spec/module_spec.rb +++ b/spec/module_spec.rb @@ -71,6 +71,51 @@ end end + describe '#group' do + it 'should error without group_id' do + expect { AnalyticsRuby.group :user_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without user_id' do + expect { AnalyticsRuby.group :group_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + AnalyticsRuby.group AnalyticsRubyHelpers::Queued::GROUP + sleep(1) + end + end + + describe '#page' do + it 'should error without user_id' do + expect { AnalyticsRuby.page :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { AnalyticsRuby.page :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + AnalyticsRuby.page AnalyticsRubyHelpers::Queued::PAGE + sleep(1) + end + end + + describe '#screen' do + it 'should error without user_id' do + expect { AnalyticsRuby.screen :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { AnalyticsRuby.screen :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + AnalyticsRuby.screen AnalyticsRubyHelpers::Queued::SCREEN + sleep(1) + end + end + describe '#flush' do it 'should flush without error' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d3cd1aa..d6946ab 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,8 +18,7 @@ module AnalyticsRubyHelpers :likes_animals => true, :instrument => 'Guitar', :age => 25 - }, - :action => 'identify' + } } ALIAS = { @@ -27,15 +26,29 @@ module AnalyticsRubyHelpers :to => 'abcd' } + GROUP = {} + + PAGE = { + :name => 'home' + } + + SCREEN = { + :name => 'main' + } + USER_ID = 1234 + GROUP_ID = 1234 - # Hashes sent to the client + # Hashes sent to the client, snake_case module Queued TRACK = TRACK.merge :user_id => USER_ID IDENTIFY = IDENTIFY.merge :user_id => USER_ID + GROUP = GROUP.merge :group_id => GROUP_ID, :user_id => USER_ID + PAGE = PAGE.merge :user_id => USER_ID + SCREEN = SCREEN.merge :user_id => USER_ID end - # Hashes which are sent from the consumer + # Hashes which are sent from the consumer, camel_cased module Requested TRACK = TRACK.merge({ :userId => USER_ID, @@ -46,5 +59,14 @@ module Requested :userId => USER_ID, :action => 'identify' }) + + GROUP = GROUP.merge({ + :groupId => GROUP_ID, + :userId => USER_ID, + :action => 'group' + }) + + PAGE = PAGE.merge :userId => USER_ID + SCREEN = SCREEN.merge :userId => USER_ID end end \ No newline at end of file From 147d451828a75e212909f7b44a5cd9c62aba43d5 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 19 Feb 2014 18:43:38 -0800 Subject: [PATCH 018/322] Release 0.6.0 * adding .group(), .page(), and .screen() calls * relaxing faraday dependency, fixes #31 --- History.md | 7 ++++++- lib/analytics-ruby/version.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index ff795f6..de0dcc6 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +0.6.0 / 2014-02-19 +================== +* adding .group(), .page(), and .screen() calls +* relaxing faraday dependency, fixes #31 + 0.5.4 / 2013-12-31 ================== * Add `requestId` fields to all requests for tracing. @@ -16,7 +21,7 @@ 0.5.0 / 2013-10-03 ================== -* Removing global Analytics alias in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. Otherwise you WILL NOT be sending analytics data. See the [setup docs for more info](https://segment.io/libraries/ruby) +* Removing global Analytics constant in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. Otherwise you WILL NOT be sending analytics data. See the [setup docs for more info](https://segment.io/libraries/ruby) 0.4.0 / 2013-08-30 ================== diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index d15802e..2cbc480 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.5.4' + VERSION = '0.6.0' end \ No newline at end of file From 207bbddbda96f149378e61a925bd6be10823f36e Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 12 Mar 2014 18:51:10 -0700 Subject: [PATCH 019/322] cleanup, removing faraday --- analytics-ruby.gemspec | 6 +--- lib/analytics-ruby/client.rb | 4 +-- lib/analytics-ruby/consumer.rb | 4 +-- lib/analytics-ruby/defaults.rb | 5 ++-- lib/analytics-ruby/json.rb | 28 ------------------ lib/analytics-ruby/request.rb | 52 +++++++++++++++++----------------- spec/consumer_spec.rb | 4 +-- 7 files changed, 36 insertions(+), 67 deletions(-) delete mode 100644 lib/analytics-ruby/json.rb diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 879e4b6..8df5ee6 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -15,11 +15,7 @@ Gem::Specification.new do |spec| spec.homepage = 'https://github.com/segmentio/analytics-ruby' spec.license = 'MIT' - spec.add_dependency 'faraday', ['>= 0.8'] - spec.add_dependency 'faraday_middleware', ['>= 0.8', '< 0.10'] - spec.add_dependency 'multi_json', ['~> 1.0'] - - # Ruby 1.8 requires json for faraday_middleware + # Ruby 1.8 requires json spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" spec.add_development_dependency('rake') diff --git a/lib/analytics-ruby/client.rb b/lib/analytics-ruby/client.rb index 278fdd3..36ce37f 100644 --- a/lib/analytics-ruby/client.rb +++ b/lib/analytics-ruby/client.rb @@ -26,11 +26,11 @@ def initialize(options = {}) @queue = Queue.new @secret = options[:secret] - @max_queue_size = options[:max_queue_size] || AnalyticsRuby::Defaults::Queue::MAX_SIZE + @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE check_secret - @consumer = AnalyticsRuby::Consumer.new @queue, @secret, options + @consumer = Consumer.new @queue, @secret, options @thread = ConsumerThread.new { @consumer.run } at_exit do diff --git a/lib/analytics-ruby/consumer.rb b/lib/analytics-ruby/consumer.rb index 4c276fd..30db8eb 100644 --- a/lib/analytics-ruby/consumer.rb +++ b/lib/analytics-ruby/consumer.rb @@ -23,7 +23,7 @@ def initialize(queue, secret, options = {}) @queue = queue @secret = secret - @batch_size = options[:batch_size] || AnalyticsRuby::Defaults::Queue::BATCH_SIZE + @batch_size = options[:batch_size] || Defaults::Queue::BATCH_SIZE @on_error = options[:on_error] || Proc.new { |status, error| } @current_batch = [] @@ -54,7 +54,7 @@ def flush end } - req = AnalyticsRuby::Request.new + req = Request.new res = req.post @secret, @current_batch @on_error.call res.status, res.error unless res.status == 200 @mutex.synchronize { diff --git a/lib/analytics-ruby/defaults.rb b/lib/analytics-ruby/defaults.rb index 4929364..8d76213 100644 --- a/lib/analytics-ruby/defaults.rb +++ b/lib/analytics-ruby/defaults.rb @@ -3,9 +3,10 @@ module AnalyticsRuby module Defaults module Request - BASE_URL = 'https://api.segment.io' unless defined? AnalyticsRuby::Defaults::Request::BASE_URL + HOST = 'api.segment.io' unless defined? AnalyticsRuby::Defaults::Request::HOST + PORT = 443 unless defined? AnalyticsRuby::Defaults::Request::PORT PATH = '/v1/import' unless defined? AnalyticsRuby::Defaults::Request::PATH - SSL = { :verify => false } unless defined? AnalyticsRuby::Defaults::Request::SSL + SSL = true unless defined? AnalyticsRuby::Defaults::Request::SSL HEADERS = { :accept => 'application/json' } unless defined? AnalyticsRuby::Defaults::Request::HEADERS RETRIES = 4 unless defined? AnalyticsRuby::Defaults::Request::RETRIES BACKOFF = 30.0 unless defined? AnalyticsRuby::Defaults::Request::BACKOFF diff --git a/lib/analytics-ruby/json.rb b/lib/analytics-ruby/json.rb deleted file mode 100644 index 27d27ab..0000000 --- a/lib/analytics-ruby/json.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'multi_json' - -module AnalyticsRuby - - # JSON Wrapper module adapted from - # https://github.com/stripe/stripe-ruby/blob/master/lib/stripe/json.rb - # - # .dump was added in MultiJson 1.3 - module JSON - if MultiJson.respond_to? :dump - def self.dump(*args) - MultiJson.dump(*args) - end - - def self.load(*args) - MultiJson.load(*args) - end - else - def self.dump(*args) - MultiJson.encode(*args) - end - - def self.load(*args) - MultiJson.decode(*args) - end - end - end -end \ No newline at end of file diff --git a/lib/analytics-ruby/request.rb b/lib/analytics-ruby/request.rb index 01c3ba7..191e554 100644 --- a/lib/analytics-ruby/request.rb +++ b/lib/analytics-ruby/request.rb @@ -1,9 +1,9 @@ require 'analytics-ruby/defaults' require 'analytics-ruby/response' -require 'analytics-ruby/json' -require 'faraday' -require 'faraday_middleware' +require 'net/http' +require 'net/https' +require 'json' module AnalyticsRuby @@ -12,19 +12,20 @@ class Request # public: Creates a new request object to send analytics batch # def initialize(options = {}) - - options[:url] ||= AnalyticsRuby::Defaults::Request::BASE_URL - options[:ssl] ||= AnalyticsRuby::Defaults::Request::SSL - options[:headers] ||= AnalyticsRuby::Defaults::Request::HEADERS - @path = options[:path] || AnalyticsRuby::Defaults::Request::PATH - @retries = options[:retries] || AnalyticsRuby::Defaults::Request::RETRIES - @backoff = options[:backoff] || AnalyticsRuby::Defaults::Request::BACKOFF - - @conn = Faraday.new options do |faraday| - faraday.request :json - faraday.response :json, :content_type => /\bjson$/ - faraday.adapter Faraday.default_adapter - end + options[:host] ||= Defaults::Request::HOST + options[:port] ||= Defaults::Request::PORT + options[:ssl] ||= Defaults::Request::SSL + options[:headers] ||= Defaults::Request::HEADERS + @path = options[:path] || Defaults::Request::PATH + @retries = options[:retries] || Defaults::Request::RETRIES + @backoff = options[:backoff] || Defaults::Request::BACKOFF + + http = Net::HTTP.new(options[:host], options[:port]) + http.use_ssl = options[:ssl] + http.read_timeout = 8 + http.open_timeout = 4 + + @http = http end # public: Posts the secret and batch of messages to the API. @@ -35,27 +36,26 @@ def post(secret, batch) status, error = nil, nil remaining_retries = @retries backoff = @backoff - + headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin - res = @conn.post do |req| - req.options[:timeout] = 8 - req.options[:open_timeout] = 3 - req.url(@path) - req.body = AnalyticsRuby::JSON::dump :secret => secret, :batch => batch - end - status = res.status - error = res.body["error"] + payload = JSON.generate :secret => secret, :batch => batch + res = @http.request(Net::HTTP::Post.new(@path, headers), payload) + status = res.code.to_i + body = JSON.parse(res.body) + error = body["error"] rescue Exception => err + puts "err: #{err}" status = -1 error = "Connection error: #{err}" + puts "retries: #{remaining_retries}" unless (remaining_retries -=1).zero? sleep(backoff) retry end end - AnalyticsRuby::Response.new status, error + Response.new status, error end end end diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb index 3ae2e7c..8046e5f 100644 --- a/spec/consumer_spec.rb +++ b/spec/consumer_spec.rb @@ -24,7 +24,7 @@ it 'should not error if the endpoint is unreachable' do - Faraday::Connection.any_instance.stub(:post).and_raise(Exception) + Net::HTTP.any_instance.stub(:post).and_raise(Exception) queue = Queue.new queue << {} @@ -33,7 +33,7 @@ queue.should be_empty - Faraday::Connection.any_instance.unstub(:post) + Net::HTTP.any_instance.unstub(:post) end it 'should execute the error handler if the request is invalid' do From 7dcada2be8da4616a20fc2f417121f937ecb505a Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 12 Mar 2014 18:52:00 -0700 Subject: [PATCH 020/322] adding more travis tests --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 02ed87d..4d8ff0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,8 @@ rvm: - 1.8.7 - 1.9.2 - 1.9.3 - - 2.0.0 \ No newline at end of file + - 2.0.0 + - 2.1.0 + - ree + - ruby-head + - jruby \ No newline at end of file From 920e3287cac67cd188efd5107c6aee58bbdfd0bd Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 12 Mar 2014 18:59:32 -0700 Subject: [PATCH 021/322] removing rbx from travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d8ff0a..d7e9e41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: ruby rvm: - - rbx-18mode - - rbx-19mode - - rbx-20mode - jruby-18mode - jruby-19mode - 1.8.7 From 954396ff07abd8244237b59c0f84fafd4b99df99 Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 12 Mar 2014 19:05:32 -0700 Subject: [PATCH 022/322] Release 1.0.0 * removing faraday dependency * cleanup --- lib/analytics-ruby/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index 2cbc480..ec48552 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '0.6.0' + VERSION = '1.0.0' end \ No newline at end of file From 409905de3b0cfbe9134d2888288125ee6be786ca Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Wed, 12 Mar 2014 19:12:30 -0700 Subject: [PATCH 023/322] updating history.md --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index de0dcc6..c7118ee 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +1.0.0 / 2014-03-12 +================== +* removing faraday dependency + 0.6.0 / 2014-02-19 ================== * adding .group(), .page(), and .screen() calls From 853f0ddb6392831d8594abb882af6dc836f1b910 Mon Sep 17 00:00:00 2001 From: Alan Cohen Date: Wed, 16 Apr 2014 00:51:25 -0400 Subject: [PATCH 024/322] Implement AnalyticsRuby#initialized? Check if the library (client) has been initialized --- lib/analytics-ruby.rb | 6 +++++- spec/module_spec.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb index ce17440..a0dd0fe 100644 --- a/lib/analytics-ruby.rb +++ b/lib/analytics-ruby.rb @@ -42,6 +42,10 @@ def flush return false unless @client @client.flush end + + def initialized? + !!@client + end end extend ClassMethods -end \ No newline at end of file +end diff --git a/spec/module_spec.rb b/spec/module_spec.rb index b3e6681..a860c78 100644 --- a/spec/module_spec.rb +++ b/spec/module_spec.rb @@ -123,4 +123,14 @@ AnalyticsRuby.flush end end + + describe "#initialized?" do + + context "when initialized" do + it "should return true" do + AnalyticsRuby.init :secret => AnalyticsRubyHelpers::SECRET + expect(AnalyticsRuby.initialized?).to be_true + end + end + end end From 4ef75a1522df5c4755676457f69226857c8d4bbf Mon Sep 17 00:00:00 2001 From: Calvin French-Owen Date: Thu, 17 Apr 2014 13:27:27 -0700 Subject: [PATCH 025/322] Release 1.1.0 * adding .initialized? method --- History.md | 5 +++++ lib/analytics-ruby/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c7118ee..61219cf 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ + +1.1.0 / 2014-04-17 +================== +* adding .initialized? by [@lumberj](https://github.com/lumberj) + 1.0.0 / 2014-03-12 ================== * removing faraday dependency diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb index ec48552..a58a2b4 100644 --- a/lib/analytics-ruby/version.rb +++ b/lib/analytics-ruby/version.rb @@ -1,3 +1,3 @@ module AnalyticsRuby - VERSION = '1.0.0' + VERSION = '1.1.0' end \ No newline at end of file From 25fd1ac2a971e3e7f2d829e9f03bde599229bd80 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 14:25:06 -0500 Subject: [PATCH 026/322] Namespace under Segment::Analytics (#44, #43, #15) --- analytics-ruby.gemspec | 7 +- lib/analytics-ruby.rb | 51 ---- lib/analytics-ruby/client.rb | 311 ------------------------ lib/analytics-ruby/consumer.rb | 76 ------ lib/analytics-ruby/defaults.rb | 21 -- lib/analytics-ruby/request.rb | 61 ----- lib/analytics-ruby/response.rb | 17 -- lib/analytics-ruby/util.rb | 42 ---- lib/analytics-ruby/version.rb | 3 - lib/segment.rb | 1 + lib/segment/analytics.rb | 38 +++ lib/segment/analytics/client.rb | 311 ++++++++++++++++++++++++ lib/segment/analytics/consumer.rb | 75 ++++++ lib/segment/analytics/defaults.rb | 20 ++ lib/segment/analytics/request.rb | 61 +++++ lib/segment/analytics/response.rb | 16 ++ lib/segment/analytics/utils.rb | 47 ++++ lib/segment/analytics/version.rb | 6 + spec/client_spec.rb | 212 ---------------- spec/consumer_spec.rb | 103 -------- spec/module_spec.rb | 136 ----------- spec/segment/analytics/client_spec.rb | 209 ++++++++++++++++ spec/segment/analytics/consumer_spec.rb | 101 ++++++++ spec/segment/analytics_spec.rb | 135 ++++++++++ spec/spec_helper.rb | 114 ++++----- 25 files changed, 1080 insertions(+), 1094 deletions(-) delete mode 100644 lib/analytics-ruby.rb delete mode 100644 lib/analytics-ruby/client.rb delete mode 100644 lib/analytics-ruby/consumer.rb delete mode 100644 lib/analytics-ruby/defaults.rb delete mode 100644 lib/analytics-ruby/request.rb delete mode 100644 lib/analytics-ruby/response.rb delete mode 100644 lib/analytics-ruby/util.rb delete mode 100644 lib/analytics-ruby/version.rb create mode 100644 lib/segment.rb create mode 100644 lib/segment/analytics.rb create mode 100644 lib/segment/analytics/client.rb create mode 100644 lib/segment/analytics/consumer.rb create mode 100644 lib/segment/analytics/defaults.rb create mode 100644 lib/segment/analytics/request.rb create mode 100644 lib/segment/analytics/response.rb create mode 100644 lib/segment/analytics/utils.rb create mode 100644 lib/segment/analytics/version.rb delete mode 100644 spec/client_spec.rb delete mode 100644 spec/consumer_spec.rb delete mode 100644 spec/module_spec.rb create mode 100644 spec/segment/analytics/client_spec.rb create mode 100644 spec/segment/analytics/consumer_spec.rb create mode 100644 spec/segment/analytics_spec.rb diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 8df5ee6..d5fba7e 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -1,11 +1,8 @@ -$:.push File.expand_path('../lib', __FILE__) - -require 'analytics-ruby/version' - +require File.expand_path('../lib/segment/analytics/version', __FILE__) Gem::Specification.new do |spec| spec.name = 'analytics-ruby' - spec.version = AnalyticsRuby::VERSION + spec.version = Segment::Analytics::VERSION spec.files = Dir.glob('**/*') spec.require_paths = ['lib'] spec.summary = 'Segment.io analytics library' diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb deleted file mode 100644 index a0dd0fe..0000000 --- a/lib/analytics-ruby.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'analytics-ruby/version' -require 'analytics-ruby/client' - -module AnalyticsRuby - module ClassMethods - # By default use a single client for the module - def init(options = {}) - @client = AnalyticsRuby::Client.new options - end - - def track(options) - return false unless @client - @client.track options - end - - def identify(options) - return false unless @client - @client.identify options - end - - def alias(options) - return false unless @client - @client.alias options - end - - def group(options) - return false unless @client - @client.group options - end - - def page(options) - return false unless @client - @client.page options - end - - def screen(options) - return false unless @client - @client.screen options - end - - def flush - return false unless @client - @client.flush - end - - def initialized? - !!@client - end - end - extend ClassMethods -end diff --git a/lib/analytics-ruby/client.rb b/lib/analytics-ruby/client.rb deleted file mode 100644 index 36ce37f..0000000 --- a/lib/analytics-ruby/client.rb +++ /dev/null @@ -1,311 +0,0 @@ - -require 'time' -require 'thread' -require 'analytics-ruby/defaults' -require 'analytics-ruby/consumer' -require 'analytics-ruby/request' -require 'analytics-ruby/util' - -module AnalyticsRuby - - class Client - - # Sub-class thread so we have a named thread (useful for debugging in Thread.list). - class ConsumerThread < Thread - end - - # public: Creates a new client - # - # options - Hash - # :secret - String of your project's secret - # :max_queue_size - Fixnum of the max calls to remain queued (optional) - # :on_error - Proc which handles error calls from the API - def initialize(options = {}) - - Util.symbolize_keys! options - - @queue = Queue.new - @secret = options[:secret] - @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE - - check_secret - - @consumer = Consumer.new @queue, @secret, options - @thread = ConsumerThread.new { @consumer.run } - - at_exit do - # Let the consumer thread know it should exit. - @thread[:should_exit] = true - - # Push a flag value to the consumer queue in case it's blocked waiting for a value. This will allow it - # to continue its normal chain of processing, giving it a chance to exit. - @queue << nil - end - end - - # public: Synchronously waits until the consumer has flushed the queue. - # Use only for scripts which are not long-running, and will - # specifically exit - # - def flush - while !@queue.empty? || @consumer.is_requesting? - sleep(0.1) - end - end - - # public: Tracks an event - # - # options - Hash - # :event - String of event name. - # :user_id - String of the user id. - # :properties - Hash of event properties. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :context - Hash of context. (optional) - def track(options) - - check_secret - - Util.symbolize_keys! options - - event = options[:event] - user_id = options[:user_id].to_s - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - ensure_user user_id - check_timestamp timestamp - - if event.nil? || event.empty? - fail ArgumentError, 'Must supply event as a non-empty string' - end - - fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash - Util.isoify_dates! properties - - add_context context - - enqueue({ - :event => event, - :userId => user_id, - :context => context, - :properties => properties, - :timestamp => timestamp.iso8601, - :action => 'track' - }) - end - - # public: Identifies a user - # - # options - Hash - # :user_id - String of the user id - # :traits - Hash of user traits. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :context - Hash of context. (optional) - def identify(options) - - check_secret - - Util.symbolize_keys! options - - user_id = options[:user_id].to_s - traits = options[:traits] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - ensure_user user_id - check_timestamp timestamp - - fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash - Util.isoify_dates! traits - - add_context context - - enqueue({ - :userId => user_id, - :context => context, - :traits => traits, - :timestamp => timestamp.iso8601, - :action => 'identify' - }) - end - - # public: Aliases a user from one id to another - # - # options - Hash - # :from - String of the id to alias from - # :to - String of the id to alias to - # :timestamp - Time of when the alias occured (optional) - # :context - Hash of context (optional) - def alias(options) - check_secret - Util.symbolize_keys! options - from = options[:from].to_s - to = options[:to].to_s - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - ensure_user from - ensure_user to - check_timestamp timestamp - add_context context - - enqueue({ - :from => from, - :to => to, - :context => context, - :timestamp => timestamp.iso8601, - :action => 'alias' - }) - end - - # public: Associates a user identity with a group. - # - # options - Hash - # :from - String of the id to alias from - # :to - String of the id to alias to - # :timestamp - Time of when the alias occured (optional) - # :context - Hash of context (optional) - def group(options) - check_secret - Util.symbolize_keys! options - group_id = options[:group_id].to_s - user_id = options[:user_id].to_s - traits = options[:traits] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash - - ensure_user group_id - ensure_user user_id - check_timestamp timestamp - add_context context - - enqueue({ - :groupId => group_id, - :userId => user_id, - :traits => traits, - :context => context, - :timestamp => timestamp.iso8601, - :action => 'group' - }) - end - - # public: Records a page view - # - # options - Hash - # :user_id - String of the id to alias from - # :name - String name of the page - # :properties - Hash of page properties (optional) - # :timestamp - Time of when the pageview occured (optional) - # :context - Hash of context (optional) - def page(options) - check_secret - Util.symbolize_keys! options - user_id = options[:user_id].to_s - name = options[:name].to_s - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - fail ArgumentError, '.name must be a string' unless !name.empty? - fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash - Util.isoify_dates! properties - - ensure_user user_id - check_timestamp timestamp - add_context context - - enqueue({ - :userId => user_id, - :name => name, - :properties => properties, - :context => context, - :timestamp => timestamp.iso8601, - :action => 'page' - }) - end - # public: Records a screen view (for a mobile app) - # - # options - Hash - # :user_id - String of the id to alias from - # :name - String name of the screen - # :properties - Hash of screen properties (optional) - # :timestamp - Time of when the screen occured (optional) - # :context - Hash of context (optional) - def screen(options) - check_secret - Util.symbolize_keys! options - user_id = options[:user_id].to_s - name = options[:name].to_s - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} - - fail ArgumentError, '.name must be a string' if name.empty? - fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash - Util.isoify_dates! properties - - ensure_user user_id - check_timestamp timestamp - add_context context - - enqueue({ - :userId => user_id, - :name => name, - :properties => properties, - :context => context, - :timestamp => timestamp.iso8601, - :action => 'screen' - }) - end - - # public: Returns the number of queued messages - # - # returns Fixnum of messages in the queue - def queued_messages - @queue.length - end - - private - - # private: Enqueues the action. - # - # returns Boolean of whether the item was added to the queue. - def enqueue(action) - # add our request id for tracing purposes - action[:requestId] = Util.uid - - queue_full = @queue.length >= @max_queue_size - @queue << action unless queue_full - - !queue_full - end - - # private: Ensures that a user id was passed in. - # - # user_id - String of the user id - # - def ensure_user(user_id) - fail ArgumentError, 'Must supply a non-empty user_id' if user_id.empty? - end - - # private: Adds contextual information to the call - # - # context - Hash of call context - def add_context(context) - context[:library] = 'analytics-ruby' - end - - # private: Checks that the secret is properly initialized - def check_secret - fail 'Secret must be initialized' if @secret.nil? - end - - # private: Checks the timstamp option to make sure it is a Time. - def check_timestamp(timestamp) - fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time - end - end -end \ No newline at end of file diff --git a/lib/analytics-ruby/consumer.rb b/lib/analytics-ruby/consumer.rb deleted file mode 100644 index 30db8eb..0000000 --- a/lib/analytics-ruby/consumer.rb +++ /dev/null @@ -1,76 +0,0 @@ - -require 'analytics-ruby/defaults' -require 'analytics-ruby/request' -require 'analytics-ruby/util' - -module AnalyticsRuby - - class Consumer - - # public: Creates a new consumer - # - # The consumer continuously takes messages off the queue - # and makes requests to the segment.io api - # - # queue - Queue synchronized between client and consumer - # secret - String of the project's secret - # options - Hash of consumer options - # batch_size - Fixnum of how many items to send in a batch - # on_error - Proc of what to do on an error - # - def initialize(queue, secret, options = {}) - Util.symbolize_keys! options - - @queue = queue - @secret = secret - @batch_size = options[:batch_size] || Defaults::Queue::BATCH_SIZE - @on_error = options[:on_error] || Proc.new { |status, error| } - - @current_batch = [] - - @mutex = Mutex.new - end - - # public: Continuously runs the loop to check for new events - # - def run - until Thread.current[:should_exit] - flush - end - end - - # public: Flush some events from our queue - # - def flush - # Block until we have something to send - item = @queue.pop - return if item.nil? - - # Synchronize on additions to the current batch - @mutex.synchronize { - @current_batch << item - until @current_batch.length >= @batch_size || @queue.empty? - @current_batch << @queue.pop - end - } - - req = Request.new - res = req.post @secret, @current_batch - @on_error.call res.status, res.error unless res.status == 200 - @mutex.synchronize { - @current_batch = [] - } - end - - # public: Check whether we have outstanding requests. - # - def is_requesting? - requesting = nil - @mutex.synchronize { - requesting = !@current_batch.empty? - } - requesting - end - - end -end \ No newline at end of file diff --git a/lib/analytics-ruby/defaults.rb b/lib/analytics-ruby/defaults.rb deleted file mode 100644 index 8d76213..0000000 --- a/lib/analytics-ruby/defaults.rb +++ /dev/null @@ -1,21 +0,0 @@ - -module AnalyticsRuby - module Defaults - - module Request - HOST = 'api.segment.io' unless defined? AnalyticsRuby::Defaults::Request::HOST - PORT = 443 unless defined? AnalyticsRuby::Defaults::Request::PORT - PATH = '/v1/import' unless defined? AnalyticsRuby::Defaults::Request::PATH - SSL = true unless defined? AnalyticsRuby::Defaults::Request::SSL - HEADERS = { :accept => 'application/json' } unless defined? AnalyticsRuby::Defaults::Request::HEADERS - RETRIES = 4 unless defined? AnalyticsRuby::Defaults::Request::RETRIES - BACKOFF = 30.0 unless defined? AnalyticsRuby::Defaults::Request::BACKOFF - end - - module Queue - BATCH_SIZE = 100 unless defined? AnalyticsRuby::Defaults::Queue::BATCH_SIZE - MAX_SIZE = 10000 unless defined? AnalyticsRuby::Defaults::Queue::MAX_SIZE - end - - end -end diff --git a/lib/analytics-ruby/request.rb b/lib/analytics-ruby/request.rb deleted file mode 100644 index 191e554..0000000 --- a/lib/analytics-ruby/request.rb +++ /dev/null @@ -1,61 +0,0 @@ - -require 'analytics-ruby/defaults' -require 'analytics-ruby/response' -require 'net/http' -require 'net/https' -require 'json' - -module AnalyticsRuby - - class Request - - # public: Creates a new request object to send analytics batch - # - def initialize(options = {}) - options[:host] ||= Defaults::Request::HOST - options[:port] ||= Defaults::Request::PORT - options[:ssl] ||= Defaults::Request::SSL - options[:headers] ||= Defaults::Request::HEADERS - @path = options[:path] || Defaults::Request::PATH - @retries = options[:retries] || Defaults::Request::RETRIES - @backoff = options[:backoff] || Defaults::Request::BACKOFF - - http = Net::HTTP.new(options[:host], options[:port]) - http.use_ssl = options[:ssl] - http.read_timeout = 8 - http.open_timeout = 4 - - @http = http - end - - # public: Posts the secret and batch of messages to the API. - # - # returns - Response of the status and error if it exists - def post(secret, batch) - - status, error = nil, nil - remaining_retries = @retries - backoff = @backoff - headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } - begin - payload = JSON.generate :secret => secret, :batch => batch - res = @http.request(Net::HTTP::Post.new(@path, headers), payload) - status = res.code.to_i - body = JSON.parse(res.body) - error = body["error"] - - rescue Exception => err - puts "err: #{err}" - status = -1 - error = "Connection error: #{err}" - puts "retries: #{remaining_retries}" - unless (remaining_retries -=1).zero? - sleep(backoff) - retry - end - end - - Response.new status, error - end - end -end diff --git a/lib/analytics-ruby/response.rb b/lib/analytics-ruby/response.rb deleted file mode 100644 index 100fdef..0000000 --- a/lib/analytics-ruby/response.rb +++ /dev/null @@ -1,17 +0,0 @@ - -module AnalyticsRuby - - class Response - - attr_reader :status - attr_reader :error - - # public: Simple class to wrap responses from the API - # - # - def initialize(status = 200, error = nil) - @status = status - @error = error - end - end -end \ No newline at end of file diff --git a/lib/analytics-ruby/util.rb b/lib/analytics-ruby/util.rb deleted file mode 100644 index 1be0494..0000000 --- a/lib/analytics-ruby/util.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Util - - # public: Return a new hash with keys converted from strings to symbols - # - def self.symbolize_keys(hash) - hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo } - end - - # public: Convert hash keys from strings to symbols in place - # - def self.symbolize_keys!(hash) - hash.replace symbolize_keys hash - end - - # public: Return a new hash with keys as strings - # - def self.stringify_keys(hash) - hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo } - end - - # public: Returns a new hash with all the date values in the into iso8601 - # strings - # - def self.isoify_dates(hash) - hash.inject({}) { |memo, (k, v)| - memo[k] = v.respond_to?(:iso8601) ? v.iso8601 : v - memo - } - end - - # public: Converts all the date values in the into iso8601 strings in place - # - def self.isoify_dates!(hash) - hash.replace isoify_dates hash - end - - # public: Returns a uid string - # - def self.uid - (0..16).to_a.map{|x| rand(16).to_s(16)}.join - end -end \ No newline at end of file diff --git a/lib/analytics-ruby/version.rb b/lib/analytics-ruby/version.rb deleted file mode 100644 index a58a2b4..0000000 --- a/lib/analytics-ruby/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module AnalyticsRuby - VERSION = '1.1.0' -end \ No newline at end of file diff --git a/lib/segment.rb b/lib/segment.rb new file mode 100644 index 0000000..1465165 --- /dev/null +++ b/lib/segment.rb @@ -0,0 +1 @@ +require 'segment/analytics' diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb new file mode 100644 index 0000000..d3d3dbf --- /dev/null +++ b/lib/segment/analytics.rb @@ -0,0 +1,38 @@ +require 'segment/analytics/defaults' +require 'segment/analytics/utils' +require 'segment/analytics/version' +require 'segment/analytics/client' +require 'segment/analytics/consumer' +require 'segment/analytics/request' +require 'segment/analytics/response' + +module Segment + module Analytics + extend self + + def setup options = {} + @options = options + end + alias_method :init, :setup + + def setup? + !!@options + end + alias_method :"initialized?", :"setup?" + + def client + @client ||= Segment::Analytics::Client.new @options + end + + def method_missing message, *args, &block + if Segment::Analytics::Client.method_defined? message + if setup? + client.send message, *args, &block + end + else + super + end + end + end +end + diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb new file mode 100644 index 0000000..0967a04 --- /dev/null +++ b/lib/segment/analytics/client.rb @@ -0,0 +1,311 @@ +require 'thread' +require 'time' +require 'segment/analytics/utils' +require 'segment/analytics/consumer' +require 'segment/analytics/defaults' + +module Segment + module Analytics + class Client + include Segment::Analytics::Utils + + # public: Creates a new client + # + # options - Hash + # :secret - String of your project's secret + # :max_queue_size - Fixnum of the max calls to remain queued (optional) + # :on_error - Proc which handles error calls from the API + def initialize options = {} + symbolize_keys! options + + @queue = Queue.new + @secret = options[:secret] + @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE + + check_secret + + @consumer = Consumer.new @queue, @secret, options + @thread = ConsumerThread.new { @consumer.run } + + at_exit do + # Let the consumer thread know it should exit. + @thread[:should_exit] = true + + # Push a flag value to the consumer queue in case it's blocked waiting for a value. This will allow it + # to continue its normal chain of processing, giving it a chance to exit. + @queue << nil + end + end + + # public: Synchronously waits until the consumer has flushed the queue. + # Use only for scripts which are not long-running, and will + # specifically exit + # + def flush + while !@queue.empty? || @consumer.is_requesting? + sleep(0.1) + end + end + + # public: Tracks an event + # + # options - Hash + # :event - String of event name. + # :user_id - String of the user id. + # :properties - Hash of event properties. (optional) + # :timestamp - Time of when the event occurred. (optional) + # :context - Hash of context. (optional) + def track options + + check_secret + + symbolize_keys! options + + event = options[:event] + user_id = options[:user_id].to_s + properties = options[:properties] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + ensure_user user_id + check_timestamp timestamp + + if event.nil? || event.empty? + fail ArgumentError, 'Must supply event as a non-empty string' + end + + fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash + isoify_dates! properties + + add_context context + + enqueue({ + :event => event, + :userId => user_id, + :context => context, + :properties => properties, + :timestamp => timestamp.iso8601, + :action => 'track' + }) + end + + # public: Identifies a user + # + # options - Hash + # :user_id - String of the user id + # :traits - Hash of user traits. (optional) + # :timestamp - Time of when the event occurred. (optional) + # :context - Hash of context. (optional) + def identify options + + check_secret + + symbolize_keys! options + + user_id = options[:user_id].to_s + traits = options[:traits] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + ensure_user user_id + check_timestamp timestamp + + fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash + isoify_dates! traits + + add_context context + + enqueue({ + :userId => user_id, + :context => context, + :traits => traits, + :timestamp => timestamp.iso8601, + :action => 'identify' + }) + end + + # public: Aliases a user from one id to another + # + # options - Hash + # :from - String of the id to alias from + # :to - String of the id to alias to + # :timestamp - Time of when the alias occured (optional) + # :context - Hash of context (optional) + def alias(options) + check_secret + symbolize_keys! options + from = options[:from].to_s + to = options[:to].to_s + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + ensure_user from + ensure_user to + check_timestamp timestamp + add_context context + + enqueue({ + :from => from, + :to => to, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'alias' + }) + end + + # public: Associates a user identity with a group. + # + # options - Hash + # :from - String of the id to alias from + # :to - String of the id to alias to + # :timestamp - Time of when the alias occured (optional) + # :context - Hash of context (optional) + def group(options) + check_secret + symbolize_keys! options + group_id = options[:group_id].to_s + user_id = options[:user_id].to_s + traits = options[:traits] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash + + ensure_user group_id + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :groupId => group_id, + :userId => user_id, + :traits => traits, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'group' + }) + end + + # public: Records a page view + # + # options - Hash + # :user_id - String of the id to alias from + # :name - String name of the page + # :properties - Hash of page properties (optional) + # :timestamp - Time of when the pageview occured (optional) + # :context - Hash of context (optional) + def page(options) + check_secret + symbolize_keys! options + user_id = options[:user_id].to_s + name = options[:name].to_s + properties = options[:properties] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.name must be a string' unless !name.empty? + fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + isoify_dates! properties + + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :userId => user_id, + :name => name, + :properties => properties, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'page' + }) + end + # public: Records a screen view (for a mobile app) + # + # options - Hash + # :user_id - String of the id to alias from + # :name - String name of the screen + # :properties - Hash of screen properties (optional) + # :timestamp - Time of when the screen occured (optional) + # :context - Hash of context (optional) + def screen(options) + check_secret + symbolize_keys! options + user_id = options[:user_id].to_s + name = options[:name].to_s + properties = options[:properties] || {} + timestamp = options[:timestamp] || Time.new + context = options[:context] || {} + + fail ArgumentError, '.name must be a string' if name.empty? + fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + isoify_dates! properties + + ensure_user user_id + check_timestamp timestamp + add_context context + + enqueue({ + :userId => user_id, + :name => name, + :properties => properties, + :context => context, + :timestamp => timestamp.iso8601, + :action => 'screen' + }) + end + + # public: Returns the number of queued messages + # + # returns Fixnum of messages in the queue + def queued_messages + @queue.length + end + + private + + # private: Enqueues the action. + # + # returns Boolean of whether the item was added to the queue. + def enqueue(action) + # add our request id for tracing purposes + action[:requestId] = uid + + queue_full = @queue.length >= @max_queue_size + @queue << action unless queue_full + + !queue_full + end + + # private: Ensures that a user id was passed in. + # + # user_id - String of the user id + # + def ensure_user(user_id) + fail ArgumentError, 'Must supply a non-empty user_id' if user_id.empty? + end + + # private: Adds contextual information to the call + # + # context - Hash of call context + def add_context(context) + context[:library] = 'analytics-ruby' + end + + # private: Checks that the secret is properly initialized + def check_secret + fail 'Secret must be initialized' if @secret.nil? + end + + # private: Checks the timstamp option to make sure it is a Time. + def check_timestamp(timestamp) + fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time + end + + # Sub-class thread so we have a named thread (useful for debugging in Thread.list). + class ConsumerThread < Thread + end + end + end +end + diff --git a/lib/segment/analytics/consumer.rb b/lib/segment/analytics/consumer.rb new file mode 100644 index 0000000..e92621d --- /dev/null +++ b/lib/segment/analytics/consumer.rb @@ -0,0 +1,75 @@ +require 'segment/analytics/defaults' +require 'segment/analytics/utils' +require 'segment/analytics/defaults' +require 'segment/analytics/request' + +module Segment + module Analytics + class Consumer + include Segment::Analytics::Utils + include Segment::Analytics::Defaults + + # public: Creates a new consumer + # + # The consumer continuously takes messages off the queue + # and makes requests to the segment.io api + # + # queue - Queue synchronized between client and consumer + # secret - String of the project's secret + # options - Hash of consumer options + # batch_size - Fixnum of how many items to send in a batch + # on_error - Proc of what to do on an error + # + def initialize(queue, secret, options = {}) + symbolize_keys! options + @queue = queue + @secret = secret + @batch_size = options[:batch_size] || Queue::BATCH_SIZE + @on_error = options[:on_error] || Proc.new { |status, error| } + @current_batch = [] + @mutex = Mutex.new + end + + # public: Continuously runs the loop to check for new events + # + def run + until Thread.current[:should_exit] + flush + end + end + + # public: Flush some events from our queue + # + def flush + # Block until we have something to send + item = @queue.pop + return if item.nil? + + # Synchronize on additions to the current batch + @mutex.synchronize { + @current_batch << item + until @current_batch.length >= @batch_size || @queue.empty? + @current_batch << @queue.pop + end + } + + req = Request.new + res = req.post @secret, @current_batch + @on_error.call res.status, res.error unless res.status == 200 + @mutex.synchronize { + @current_batch = [] + } + end + + # public: Check whether we have outstanding requests. + # + def is_requesting? + requesting = nil + @mutex.synchronize { + requesting = !@current_batch.empty? + } + requesting + end + end + end +end diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb new file mode 100644 index 0000000..b5a9b56 --- /dev/null +++ b/lib/segment/analytics/defaults.rb @@ -0,0 +1,20 @@ +module Segment + module Analytics + module Defaults + module Request + HOST = 'api.segment.io' + PORT = 443 + PATH = '/v1/import' + SSL = true + HEADERS = { :accept => 'application/json' } + RETRIES = 4 + BACKOFF = 30.0 + end + + module Queue + BATCH_SIZE = 100 + MAX_SIZE = 10000 + end + end + end +end diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb new file mode 100644 index 0000000..438b71e --- /dev/null +++ b/lib/segment/analytics/request.rb @@ -0,0 +1,61 @@ +require 'segment/analytics/defaults' +require 'segment/analytics/response' +require 'net/http' +require 'net/https' +require 'json' + +module Segment + module Analytics + class Request + include Segment::Analytics::Defaults::Request + + # public: Creates a new request object to send analytics batch + # + def initialize(options = {}) + options[:host] ||= HOST + options[:port] ||= PORT + options[:ssl] ||= SSL + options[:headers] ||= HEADERS + @path = options[:path] || PATH + @retries = options[:retries] || RETRIES + @backoff = options[:backoff] || BACKOFF + + http = Net::HTTP.new(options[:host], options[:port]) + http.use_ssl = options[:ssl] + http.read_timeout = 8 + http.open_timeout = 4 + + @http = http + end + + # public: Posts the secret and batch of messages to the API. + # + # returns - Response of the status and error if it exists + def post(secret, batch) + status, error = nil, nil + remaining_retries = @retries + backoff = @backoff + headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } + begin + payload = JSON.generate :secret => secret, :batch => batch + res = @http.request(Net::HTTP::Post.new(@path, headers), payload) + status = res.code.to_i + body = JSON.parse(res.body) + error = body["error"] + + rescue Exception => err + puts "err: #{err}" + status = -1 + error = "Connection error: #{err}" + puts "retries: #{remaining_retries}" + unless (remaining_retries -=1).zero? + sleep(backoff) + retry + end + end + + Response.new status, error + end + end + end +end diff --git a/lib/segment/analytics/response.rb b/lib/segment/analytics/response.rb new file mode 100644 index 0000000..844baed --- /dev/null +++ b/lib/segment/analytics/response.rb @@ -0,0 +1,16 @@ +module Segment + module Analytics + class Response + attr_reader :status, :error + + # public: Simple class to wrap responses from the API + # + # + def initialize(status = 200, error = nil) + @status = status + @error = error + end + end + end +end + diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb new file mode 100644 index 0000000..d02a150 --- /dev/null +++ b/lib/segment/analytics/utils.rb @@ -0,0 +1,47 @@ +module Segment + module Analytics + module Utils + extend self + + # public: Return a new hash with keys converted from strings to symbols + # + def symbolize_keys(hash) + hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo } + end + + # public: Convert hash keys from strings to symbols in place + # + def symbolize_keys!(hash) + hash.replace symbolize_keys hash + end + + # public: Return a new hash with keys as strings + # + def stringify_keys(hash) + hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo } + end + + # public: Returns a new hash with all the date values in the into iso8601 + # strings + # + def isoify_dates(hash) + hash.inject({}) { |memo, (k, v)| + memo[k] = v.respond_to?(:iso8601) ? v.iso8601 : v + memo + } + end + + # public: Converts all the date values in the into iso8601 strings in place + # + def isoify_dates!(hash) + hash.replace isoify_dates hash + end + + # public: Returns a uid string + # + def uid + (0..16).to_a.map{|x| rand(16).to_s(16)}.join + end + end + end +end diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb new file mode 100644 index 0000000..3585cba --- /dev/null +++ b/lib/segment/analytics/version.rb @@ -0,0 +1,6 @@ +module Segment + module Analytics + VERSION = '1.1.0' + end +end + diff --git a/spec/client_spec.rb b/spec/client_spec.rb deleted file mode 100644 index 733569e..0000000 --- a/spec/client_spec.rb +++ /dev/null @@ -1,212 +0,0 @@ -require 'analytics-ruby' -require 'spec_helper' - - -describe AnalyticsRuby::Client do - - describe '#init' do - - it 'should error if no secret is supplied' do - expect { AnalyticsRuby::Client.new }.to raise_error(RuntimeError) - end - - it 'should not error if a secret is supplied' do - AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should not error if a secret is supplied as a string' do - AnalyticsRuby::Client.new 'secret' => AnalyticsRubyHelpers::SECRET - end - end - - describe '#track' do - - before(:all) do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - @client.instance_variable_get(:@thread).kill - @queue = @client.instance_variable_get :@queue - end - - it 'should error without an event' do - expect { @client.track(:user_id => 'user') }.to raise_error(ArgumentError) - end - - it 'should error without a user_id' do - expect { @client.track(:event => 'Event') }.to raise_error(ArgumentError) - end - - it 'should error if properties is not a hash' do - expect { - @client.track({ - :user_id => 'user', - :event => 'Event', - :properties => [1,2,3] - }) - }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.track AnalyticsRubyHelpers::Queued::TRACK - @queue.pop - end - - it 'should not error when given string keys' do - @client.track Util.stringify_keys(AnalyticsRubyHelpers::Queued::TRACK) - @queue.pop - end - - it 'should convert Time properties into iso8601 format' do - @client.track({ - :user_id => 'user', - :event => 'Event', - :properties => { - :time => Time.utc(2013), - :nottime => 'x' - } - }) - message = @queue.pop - message[:properties][:time].should == '2013-01-01T00:00:00Z' - message[:properties][:nottime].should == 'x' - end - end - - - describe '#identify' do - - before(:all) do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - @client.instance_variable_get(:@thread).kill - @queue = @client.instance_variable_get :@queue - end - - it 'should error without any user id' do - expect { @client.identify({}) }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.identify AnalyticsRubyHelpers::Queued::IDENTIFY - @queue.pop - end - - it 'should not error with the required options as strings' do - @client.identify Util.stringify_keys(AnalyticsRubyHelpers::Queued::IDENTIFY) - @queue.pop - end - - it 'should convert Time traits into iso8601 format' do - @client.identify({ - :user_id => 'user', - :traits => { - :time => Time.utc(2013), - :nottime => 'x' - } - }) - message = @queue.pop - message[:traits][:time].should == '2013-01-01T00:00:00Z' - message[:traits][:nottime].should == 'x' - end - end - - describe '#alias' do - before :all do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should error without from' do - expect { @client.alias :to => 1234 }.to raise_error(ArgumentError) - end - - it 'should error without to' do - expect { @client.alias :from => 1234 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.alias AnalyticsRubyHelpers::ALIAS - end - - it 'should not error with the required options as strings' do - @client.alias Util.stringify_keys(AnalyticsRubyHelpers::ALIAS) - end - end - - describe '#group' do - before :all do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should error without group_id' do - expect { @client.group :user_id => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without user_id' do - expect { @client.group :group_id => 'foo' }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.group AnalyticsRubyHelpers::Queued::GROUP - end - - it 'should not error with the required options as strings' do - @client.group Util.stringify_keys(AnalyticsRubyHelpers::Queued::GROUP) - end - end - - describe '#page' do - before :all do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should error without user_id' do - expect { @client.page :name => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without name' do - expect { @client.page :user_id => 1 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.page AnalyticsRubyHelpers::Queued::PAGE - end - - it 'should not error with the required options as strings' do - @client.page Util.stringify_keys(AnalyticsRubyHelpers::Queued::PAGE) - end - end - - describe '#screen' do - before :all do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should error without user_id' do - expect { @client.screen :name => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without name' do - expect { A@client.screen :user_id => 1 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - @client.screen AnalyticsRubyHelpers::Queued::SCREEN - end - - it 'should not error with the required options as strings' do - @client.screen Util.stringify_keys(AnalyticsRubyHelpers::Queued::SCREEN) - end - end - - describe '#flush' do - before(:all) do - @client = AnalyticsRuby::Client.new :secret => AnalyticsRubyHelpers::SECRET - end - - it 'should wait for the queue to finish on a flush' do - @client.identify AnalyticsRubyHelpers::Queued::IDENTIFY - @client.track AnalyticsRubyHelpers::Queued::TRACK - @client.flush - @client.queued_messages.should == 0 - end - end -end - - diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb deleted file mode 100644 index 8046e5f..0000000 --- a/spec/consumer_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'analytics-ruby' -require 'thread' -require 'spec_helper' - -describe AnalyticsRuby::Consumer do - - describe "#init" do - it 'accepts string keys' do - queue = Queue.new - consumer = AnalyticsRuby::Consumer.new(queue, 'secret', 'batch_size' => 100) - consumer.instance_variable_get(:@batch_size).should == 100 - end - end - - describe '#flush' do - - before :all do - AnalyticsRuby::Defaults::Request::BACKOFF = 0.1 - end - - after :all do - AnalyticsRuby::Defaults::Request::BACKOFF = 30.0 - end - - it 'should not error if the endpoint is unreachable' do - - Net::HTTP.any_instance.stub(:post).and_raise(Exception) - - queue = Queue.new - queue << {} - consumer = AnalyticsRuby::Consumer.new(queue, 'secret') - consumer.flush - - queue.should be_empty - - Net::HTTP.any_instance.unstub(:post) - end - - it 'should execute the error handler if the request is invalid' do - - AnalyticsRuby::Request.any_instance.stub(:post).and_return( - AnalyticsRuby::Response.new(400, "Some error")) - - on_error = Proc.new do |status, error| - puts "#{status}, #{error}" - end - - on_error.should_receive(:call).once - - queue = Queue.new - queue << {} - consumer = AnalyticsRuby::Consumer.new queue, 'secret', :on_error => on_error - consumer.flush - - AnalyticsRuby::Request::any_instance.unstub(:post) - - queue.should be_empty - end - - it 'should not call on_error if the request is good' do - - on_error = Proc.new do |status, error| - puts "#{status}, #{error}" - end - - on_error.should_receive(:call).at_most(0).times - - queue = Queue.new - queue << AnalyticsRubyHelpers::Requested::TRACK - consumer = AnalyticsRuby::Consumer.new queue, 'testsecret', :on_error => on_error - consumer.flush - - queue.should be_empty - end - end - - describe '#is_requesting?' do - - it 'should not return true if there isn\'t a current batch' do - - queue = Queue.new - consumer = AnalyticsRuby::Consumer.new(queue, 'testsecret') - - consumer.is_requesting?.should == false - end - - it 'should return true if there is a current batch' do - - queue = Queue.new - queue << AnalyticsRubyHelpers::Requested::TRACK - consumer = AnalyticsRuby::Consumer.new(queue, 'testsecret') - - Thread.new { - consumer.flush - consumer.is_requesting?.should == false - } - - # sleep barely long enough to let thread flush the queue. - sleep(0.001) - consumer.is_requesting?.should == true - end - end -end \ No newline at end of file diff --git a/spec/module_spec.rb b/spec/module_spec.rb deleted file mode 100644 index a860c78..0000000 --- a/spec/module_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'analytics-ruby' -require 'spec_helper' - -describe AnalyticsRuby do - - describe '#not-initialized' do - it 'should ignore calls to track if not initialized' do - expect { AnalyticsRuby.track({}) }.not_to raise_error - end - - it 'should return false on track if not initialized' do - AnalyticsRuby.track({}).should == false - end - - it 'should ignore calls to identify if not initialized' do - expect { AnalyticsRuby.identify({}) }.not_to raise_error - end - - it 'should return false on identify if not initialized' do - AnalyticsRuby.identify({}).should == false - end - end - - describe '#init' do - - it 'should successfully init' do - AnalyticsRuby.init :secret => AnalyticsRubyHelpers::SECRET - end - end - - describe '#track' do - - it 'should error without an event' do - expect { AnalyticsRuby.track :user_id => 'user' }.to raise_error(ArgumentError) - end - - it 'should error without a user_id' do - expect { AnalyticsRuby.track :event => 'Event' }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.track AnalyticsRubyHelpers::Queued::TRACK - sleep(1) - end - end - - - describe '#identify' do - it 'should error without a user_id' do - expect { AnalyticsRuby.identify :traits => {} }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.identify AnalyticsRubyHelpers::Queued::IDENTIFY - sleep(1) - end - end - - describe '#alias' do - it 'should error without from' do - expect { AnalyticsRuby.alias :to => 1234 }.to raise_error(ArgumentError) - end - - it 'should error without to' do - expect { AnalyticsRuby.alias :from => 1234 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.alias AnalyticsRubyHelpers::ALIAS - sleep(1) - end - end - - describe '#group' do - it 'should error without group_id' do - expect { AnalyticsRuby.group :user_id => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without user_id' do - expect { AnalyticsRuby.group :group_id => 'foo' }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.group AnalyticsRubyHelpers::Queued::GROUP - sleep(1) - end - end - - describe '#page' do - it 'should error without user_id' do - expect { AnalyticsRuby.page :name => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without name' do - expect { AnalyticsRuby.page :user_id => 1 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.page AnalyticsRubyHelpers::Queued::PAGE - sleep(1) - end - end - - describe '#screen' do - it 'should error without user_id' do - expect { AnalyticsRuby.screen :name => 'foo' }.to raise_error(ArgumentError) - end - - it 'should error without name' do - expect { AnalyticsRuby.screen :user_id => 1 }.to raise_error(ArgumentError) - end - - it 'should not error with the required options' do - AnalyticsRuby.screen AnalyticsRubyHelpers::Queued::SCREEN - sleep(1) - end - end - - describe '#flush' do - - it 'should flush without error' do - AnalyticsRuby.identify AnalyticsRubyHelpers::Queued::IDENTIFY - AnalyticsRuby.flush - end - end - - describe "#initialized?" do - - context "when initialized" do - it "should return true" do - AnalyticsRuby.init :secret => AnalyticsRubyHelpers::SECRET - expect(AnalyticsRuby.initialized?).to be_true - end - end - end -end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb new file mode 100644 index 0000000..15987b4 --- /dev/null +++ b/spec/segment/analytics/client_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +module Segment + module Analytics + describe Client do + describe '#init' do + it 'should error if no secret is supplied' do + expect { Client.new }.to raise_error(RuntimeError) + end + + it 'should not error if a secret is supplied' do + Client.new :secret => SECRET + end + + it 'should not error if a secret is supplied as a string' do + Client.new 'secret' => SECRET + end + end + + describe '#track' do + before(:all) do + @client = Client.new :secret => SECRET + @client.instance_variable_get(:@thread).kill + @queue = @client.instance_variable_get :@queue + end + + it 'should error without an event' do + expect { @client.track(:user_id => 'user') }.to raise_error(ArgumentError) + end + + it 'should error without a user_id' do + expect { @client.track(:event => 'Event') }.to raise_error(ArgumentError) + end + + it 'should error if properties is not a hash' do + expect { + @client.track({ + :user_id => 'user', + :event => 'Event', + :properties => [1,2,3] + }) + }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.track Queued::TRACK + @queue.pop + end + + it 'should not error when given string keys' do + @client.track Utils.stringify_keys(Queued::TRACK) + @queue.pop + end + + it 'should convert Time properties into iso8601 format' do + @client.track({ + :user_id => 'user', + :event => 'Event', + :properties => { + :time => Time.utc(2013), + :nottime => 'x' + } + }) + message = @queue.pop + message[:properties][:time].should == '2013-01-01T00:00:00Z' + message[:properties][:nottime].should == 'x' + end + end + + + describe '#identify' do + + before(:all) do + @client = Client.new :secret => SECRET + @client.instance_variable_get(:@thread).kill + @queue = @client.instance_variable_get :@queue + end + + it 'should error without any user id' do + expect { @client.identify({}) }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.identify Queued::IDENTIFY + @queue.pop + end + + it 'should not error with the required options as strings' do + @client.identify Utils.stringify_keys(Queued::IDENTIFY) + @queue.pop + end + + it 'should convert Time traits into iso8601 format' do + @client.identify({ + :user_id => 'user', + :traits => { + :time => Time.utc(2013), + :nottime => 'x' + } + }) + message = @queue.pop + message[:traits][:time].should == '2013-01-01T00:00:00Z' + message[:traits][:nottime].should == 'x' + end + end + + describe '#alias' do + before :all do + @client = Client.new :secret => SECRET + end + + it 'should error without from' do + expect { @client.alias :to => 1234 }.to raise_error(ArgumentError) + end + + it 'should error without to' do + expect { @client.alias :from => 1234 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.alias ALIAS + end + + it 'should not error with the required options as strings' do + @client.alias Utils.stringify_keys(ALIAS) + end + end + + describe '#group' do + before :all do + @client = Client.new :secret => SECRET + end + + it 'should error without group_id' do + expect { @client.group :user_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without user_id' do + expect { @client.group :group_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.group Queued::GROUP + end + + it 'should not error with the required options as strings' do + @client.group Utils.stringify_keys(Queued::GROUP) + end + end + + describe '#page' do + before :all do + @client = Client.new :secret => SECRET + end + + it 'should error without user_id' do + expect { @client.page :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { @client.page :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.page Queued::PAGE + end + + it 'should not error with the required options as strings' do + @client.page Utils.stringify_keys(Queued::PAGE) + end + end + + describe '#screen' do + before :all do + @client = Client.new :secret => SECRET + end + + it 'should error without user_id' do + expect { @client.screen :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { A@client.screen :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.screen Queued::SCREEN + end + + it 'should not error with the required options as strings' do + @client.screen Utils.stringify_keys(Queued::SCREEN) + end + end + + describe '#flush' do + before(:all) do + @client = Client.new :secret => SECRET + end + + it 'should wait for the queue to finish on a flush' do + @client.identify Queued::IDENTIFY + @client.track Queued::TRACK + @client.flush + @client.queued_messages.should == 0 + end + end + end + end +end diff --git a/spec/segment/analytics/consumer_spec.rb b/spec/segment/analytics/consumer_spec.rb new file mode 100644 index 0000000..33276c3 --- /dev/null +++ b/spec/segment/analytics/consumer_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +module Segment + module Analytics + describe Consumer do + describe "#init" do + it 'accepts string keys' do + queue = Queue.new + consumer = Segment::Analytics::Consumer.new(queue, 'secret', 'batch_size' => 100) + consumer.instance_variable_get(:@batch_size).should == 100 + end + end + + describe '#flush' do + before :all do + Segment::Analytics::Defaults::Request::BACKOFF = 0.1 + end + + after :all do + Segment::Analytics::Defaults::Request::BACKOFF = 30.0 + end + + it 'should not error if the endpoint is unreachable' do + Net::HTTP.any_instance.stub(:post).and_raise(Exception) + + queue = Queue.new + queue << {} + consumer = Segment::Analytics::Consumer.new(queue, 'secret') + consumer.flush + + queue.should be_empty + + Net::HTTP.any_instance.unstub(:post) + end + + it 'should execute the error handler if the request is invalid' do + Segment::Analytics::Request.any_instance.stub(:post).and_return( + Segment::Analytics::Response.new(400, "Some error")) + + on_error = Proc.new do |status, error| + puts "#{status}, #{error}" + end + + on_error.should_receive(:call).once + + queue = Queue.new + queue << {} + consumer = Segment::Analytics::Consumer.new queue, 'secret', :on_error => on_error + consumer.flush + + Segment::Analytics::Request::any_instance.unstub(:post) + + queue.should be_empty + end + + it 'should not call on_error if the request is good' do + + on_error = Proc.new do |status, error| + puts "#{status}, #{error}" + end + + on_error.should_receive(:call).at_most(0).times + + queue = Queue.new + queue << Requested::TRACK + consumer = Segment::Analytics::Consumer.new queue, 'testsecret', :on_error => on_error + consumer.flush + + queue.should be_empty + end + end + + describe '#is_requesting?' do + + it 'should not return true if there isn\'t a current batch' do + + queue = Queue.new + consumer = Segment::Analytics::Consumer.new(queue, 'testsecret') + + consumer.is_requesting?.should == false + end + + it 'should return true if there is a current batch' do + + queue = Queue.new + queue << Requested::TRACK + consumer = Segment::Analytics::Consumer.new(queue, 'testsecret') + + Thread.new { + consumer.flush + consumer.is_requesting?.should == false + } + + # sleep barely long enough to let thread flush the queue. + sleep(0.001) + consumer.is_requesting?.should == true + end + end + end + end +end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb new file mode 100644 index 0000000..f1a75b3 --- /dev/null +++ b/spec/segment/analytics_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' + +module Segment + module Analytics + describe '#not-initialized' do + it 'should ignore calls to track if not initialized' do + expect { Segment::Analytics.track({}) }.not_to raise_error + end + + it 'should return nil on track if not initialized' do + Segment::Analytics.track({}).should be_nil + end + + it 'should ignore calls to identify if not initialized' do + expect { Segment::Analytics.identify({}) }.not_to raise_error + end + + it 'should return nil on identify if not initialized' do + Segment::Analytics.identify({}).should be_nil + end + end + + describe '#init' do + + it 'should successfully init' do + Segment::Analytics.init :secret => SECRET + end + end + + describe '#track' do + + it 'should error without an event' do + expect { Segment::Analytics.track :user_id => 'user' }.to raise_error(ArgumentError) + end + + it 'should error without a user_id' do + expect { Segment::Analytics.track :event => 'Event' }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.track Queued::TRACK + sleep(1) + end + end + + + describe '#identify' do + it 'should error without a user_id' do + expect { Segment::Analytics.identify :traits => {} }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.identify Queued::IDENTIFY + sleep(1) + end + end + + describe '#alias' do + it 'should error without from' do + expect { Segment::Analytics.alias :to => 1234 }.to raise_error(ArgumentError) + end + + it 'should error without to' do + expect { Segment::Analytics.alias :from => 1234 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.alias ALIAS + sleep(1) + end + end + + describe '#group' do + it 'should error without group_id' do + expect { Segment::Analytics.group :user_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without user_id' do + expect { Segment::Analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.group Queued::GROUP + sleep(1) + end + end + + describe '#page' do + it 'should error without user_id' do + expect { Segment::Analytics.page :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { Segment::Analytics.page :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.page Queued::PAGE + sleep(1) + end + end + + describe '#screen' do + it 'should error without user_id' do + expect { Segment::Analytics.screen :name => 'foo' }.to raise_error(ArgumentError) + end + + it 'should error without name' do + expect { Segment::Analytics.screen :user_id => 1 }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + Segment::Analytics.screen Queued::SCREEN + sleep(1) + end + end + + describe '#flush' do + it 'should flush without error' do + Segment::Analytics.identify Queued::IDENTIFY + Segment::Analytics.flush + end + end + + describe "#initialized?" do + context "when initialized" do + it "should return true" do + Segment::Analytics.init :secret => SECRET + expect(Segment::Analytics.initialized?).to be_true + end + end + end + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d6946ab..592a019 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,72 +1,74 @@ +require 'segment/analytics' -module AnalyticsRubyHelpers +module Segment + module Analytics + SECRET = 'testsecret' - SECRET = 'testsecret' - - TRACK = { - :event => 'Ruby Library test event', - :properties => { - :type => 'Chocolate', - :is_a_lie => true, - :layers => 20, - :created => Time.new + TRACK = { + :event => 'Ruby Library test event', + :properties => { + :type => 'Chocolate', + :is_a_lie => true, + :layers => 20, + :created => Time.new + } } - } - IDENTIFY = { - :traits => { - :likes_animals => true, - :instrument => 'Guitar', - :age => 25 + IDENTIFY = { + :traits => { + :likes_animals => true, + :instrument => 'Guitar', + :age => 25 + } } - } - ALIAS = { - :from => 1234, - :to => 'abcd' - } + ALIAS = { + :from => 1234, + :to => 'abcd' + } - GROUP = {} + GROUP = {} - PAGE = { - :name => 'home' - } + PAGE = { + :name => 'home' + } - SCREEN = { - :name => 'main' - } + SCREEN = { + :name => 'main' + } - USER_ID = 1234 - GROUP_ID = 1234 + USER_ID = 1234 + GROUP_ID = 1234 - # Hashes sent to the client, snake_case - module Queued - TRACK = TRACK.merge :user_id => USER_ID - IDENTIFY = IDENTIFY.merge :user_id => USER_ID - GROUP = GROUP.merge :group_id => GROUP_ID, :user_id => USER_ID - PAGE = PAGE.merge :user_id => USER_ID - SCREEN = SCREEN.merge :user_id => USER_ID - end + # Hashes sent to the client, snake_case + module Queued + TRACK = TRACK.merge :user_id => USER_ID + IDENTIFY = IDENTIFY.merge :user_id => USER_ID + GROUP = GROUP.merge :group_id => GROUP_ID, :user_id => USER_ID + PAGE = PAGE.merge :user_id => USER_ID + SCREEN = SCREEN.merge :user_id => USER_ID + end - # Hashes which are sent from the consumer, camel_cased - module Requested - TRACK = TRACK.merge({ - :userId => USER_ID, - :action => 'track' - }) + # Hashes which are sent from the consumer, camel_cased + module Requested + TRACK = TRACK.merge({ + :userId => USER_ID, + :action => 'track' + }) - IDENTIFY = IDENTIFY.merge({ - :userId => USER_ID, - :action => 'identify' - }) + IDENTIFY = IDENTIFY.merge({ + :userId => USER_ID, + :action => 'identify' + }) - GROUP = GROUP.merge({ - :groupId => GROUP_ID, - :userId => USER_ID, - :action => 'group' - }) + GROUP = GROUP.merge({ + :groupId => GROUP_ID, + :userId => USER_ID, + :action => 'group' + }) - PAGE = PAGE.merge :userId => USER_ID - SCREEN = SCREEN.merge :userId => USER_ID + PAGE = PAGE.merge :userId => USER_ID + SCREEN = SCREEN.merge :userId => USER_ID + end end -end \ No newline at end of file +end From 1e45a661cafb06654f084656804129adb5fd2610 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 15:17:20 -0500 Subject: [PATCH 027/322] Add logging module --- lib/segment/analytics/logging.rb | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/segment/analytics/logging.rb diff --git a/lib/segment/analytics/logging.rb b/lib/segment/analytics/logging.rb new file mode 100644 index 0000000..2b7eaca --- /dev/null +++ b/lib/segment/analytics/logging.rb @@ -0,0 +1,35 @@ +require 'logger' + +module Segment + module Analytics + module Logging + class << self + def logger + @logger ||= if defined?(Rails) + Rails.logger + else + logger = Logger.new STDOUT + logger.progname = 'Segment::Analytics' + logger + end + end + + def logger= logger + @logger = logger + end + end + + def self.included base + class << base + def logger + Logging.logger + end + end + end + + def logger + Logging.logger + end + end + end +end From aa1d69ff30679d66c05c97f0ac09accd8de7c800 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 15:22:23 -0500 Subject: [PATCH 028/322] Replace puts's to use logger --- lib/segment/analytics/request.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 438b71e..08b2aab 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -1,5 +1,6 @@ require 'segment/analytics/defaults' require 'segment/analytics/response' +require 'segment/analytics/logging' require 'net/http' require 'net/https' require 'json' @@ -8,6 +9,7 @@ module Segment module Analytics class Request include Segment::Analytics::Defaults::Request + include Segment::Analytics::Logging # public: Creates a new request object to send analytics batch # @@ -44,10 +46,10 @@ def post(secret, batch) error = body["error"] rescue Exception => err - puts "err: #{err}" + logger.error err.message status = -1 error = "Connection error: #{err}" - puts "retries: #{remaining_retries}" + logger.info "retries remaining: #{remaining_retries}" unless (remaining_retries -=1).zero? sleep(backoff) retry From 03df44d95d4d6767f2fd49e3d4e692312a6a5600 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 15:22:38 -0500 Subject: [PATCH 029/322] Add log when messaged analytics before setup --- lib/segment/analytics.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index d3d3dbf..932262c 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -5,6 +5,7 @@ require 'segment/analytics/consumer' require 'segment/analytics/request' require 'segment/analytics/response' +require 'segment/analytics/logging' module Segment module Analytics @@ -28,11 +29,16 @@ def method_missing message, *args, &block if Segment::Analytics::Client.method_defined? message if setup? client.send message, *args, &block + else + logger.warn "messaged ##{message} before #setup" + nil end else super end end + + include Logging end end From 1e762465c67a83177826be68a7c277096c68fad0 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 15:28:36 -0500 Subject: [PATCH 030/322] Add shortcut to Analytics if there's no conflict --- lib/segment/analytics.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 932262c..d0fb042 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -42,3 +42,5 @@ def method_missing message, *args, &block end end +Analytics = Segment::Analytics unless defined? Analytics + From 70827c089c2541f27c3a39f20b6209144fe947b3 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 18:17:40 -0500 Subject: [PATCH 031/322] Add .ruby-version --- .ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..cb50681 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.0.0-p247 From 5d70f3985a53b8c7e622a13c5747bb3792624f91 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 8 May 2014 18:18:42 -0500 Subject: [PATCH 032/322] Add request stubbing (close #42) --- lib/segment/analytics.rb | 1 + lib/segment/analytics/request.rb | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index d0fb042..2d8a187 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -13,6 +13,7 @@ module Analytics def setup options = {} @options = options + Request.stub = @options[:stub] end alias_method :init, :setup diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 08b2aab..9977dff 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -40,16 +40,25 @@ def post(secret, batch) headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin payload = JSON.generate :secret => secret, :batch => batch - res = @http.request(Net::HTTP::Post.new(@path, headers), payload) - status = res.code.to_i - body = JSON.parse(res.body) - error = body["error"] + request = Net::HTTP::Post.new(@path, headers) + + if self.class.stub + status = 200 + error = nil + logger.debug "stubbed request to #{@path} with payload #{payload}" + else + res = @http.request(request, payload) + status = res.code.to_i + body = JSON.parse(res.body) + error = body["error"] + end rescue Exception => err logger.error err.message status = -1 error = "Connection error: #{err}" logger.info "retries remaining: #{remaining_retries}" + unless (remaining_retries -=1).zero? sleep(backoff) retry @@ -58,6 +67,10 @@ def post(secret, batch) Response.new status, error end + + class << self + attr_accessor :stub + end end end end From b4b8ba0f4ab17d573b13d26e7032847b53fc7f3f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 12:27:44 -0500 Subject: [PATCH 033/322] Fix secret -> write_key (fix #45) --- lib/segment/analytics/client.rb | 32 ++++++++++++++------------- lib/segment/analytics/consumer.rb | 8 +++---- lib/segment/analytics/request.rb | 6 ++--- spec/segment/analytics/client_spec.rb | 24 ++++++++++---------- spec/segment/analytics_spec.rb | 5 ++--- spec/spec_helper.rb | 2 +- 6 files changed, 39 insertions(+), 38 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 0967a04..e5c6ab7 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -12,19 +12,19 @@ class Client # public: Creates a new client # # options - Hash - # :secret - String of your project's secret + # :write_key - String of your project's write_key # :max_queue_size - Fixnum of the max calls to remain queued (optional) # :on_error - Proc which handles error calls from the API def initialize options = {} symbolize_keys! options @queue = Queue.new - @secret = options[:secret] + @write_key = options[:write_key] @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE - check_secret + check_write_key - @consumer = Consumer.new @queue, @secret, options + @consumer = Consumer.new @queue, @write_key, options @thread = ConsumerThread.new { @consumer.run } at_exit do @@ -56,8 +56,7 @@ def flush # :timestamp - Time of when the event occurred. (optional) # :context - Hash of context. (optional) def track options - - check_secret + check_write_key symbolize_keys! options @@ -97,8 +96,7 @@ def track options # :timestamp - Time of when the event occurred. (optional) # :context - Hash of context. (optional) def identify options - - check_secret + check_write_key symbolize_keys! options @@ -132,7 +130,8 @@ def identify options # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def alias(options) - check_secret + check_write_key + symbolize_keys! options from = options[:from].to_s to = options[:to].to_s @@ -161,7 +160,8 @@ def alias(options) # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def group(options) - check_secret + check_write_key + symbolize_keys! options group_id = options[:group_id].to_s user_id = options[:user_id].to_s @@ -195,7 +195,8 @@ def group(options) # :timestamp - Time of when the pageview occured (optional) # :context - Hash of context (optional) def page(options) - check_secret + check_write_key + symbolize_keys! options user_id = options[:user_id].to_s name = options[:name].to_s @@ -229,7 +230,8 @@ def page(options) # :timestamp - Time of when the screen occured (optional) # :context - Hash of context (optional) def screen(options) - check_secret + check_write_key + symbolize_keys! options user_id = options[:user_id].to_s name = options[:name].to_s @@ -292,9 +294,9 @@ def add_context(context) context[:library] = 'analytics-ruby' end - # private: Checks that the secret is properly initialized - def check_secret - fail 'Secret must be initialized' if @secret.nil? + # private: Checks that the write_key is properly initialized + def check_write_key + fail 'Write key must be initialized' if @write_key.nil? end # private: Checks the timstamp option to make sure it is a Time. diff --git a/lib/segment/analytics/consumer.rb b/lib/segment/analytics/consumer.rb index e92621d..43b1ca6 100644 --- a/lib/segment/analytics/consumer.rb +++ b/lib/segment/analytics/consumer.rb @@ -15,15 +15,15 @@ class Consumer # and makes requests to the segment.io api # # queue - Queue synchronized between client and consumer - # secret - String of the project's secret + # write_key - String of the project's Write key # options - Hash of consumer options # batch_size - Fixnum of how many items to send in a batch # on_error - Proc of what to do on an error # - def initialize(queue, secret, options = {}) + def initialize(queue, write_key, options = {}) symbolize_keys! options @queue = queue - @secret = secret + @write_key = write_key @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || Proc.new { |status, error| } @current_batch = [] @@ -54,7 +54,7 @@ def flush } req = Request.new - res = req.post @secret, @current_batch + res = req.post @write_key, @current_batch @on_error.call res.status, res.error unless res.status == 200 @mutex.synchronize { @current_batch = [] diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 9977dff..1d5d0cb 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -30,16 +30,16 @@ def initialize(options = {}) @http = http end - # public: Posts the secret and batch of messages to the API. + # public: Posts the write key and batch of messages to the API. # # returns - Response of the status and error if it exists - def post(secret, batch) + def post(write_key, batch) status, error = nil, nil remaining_retries = @retries backoff = @backoff headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin - payload = JSON.generate :secret => secret, :batch => batch + payload = JSON.generate :writeKey => write_key, :batch => batch request = Net::HTTP::Post.new(@path, headers) if self.class.stub diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 15987b4..e34f1db 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -4,22 +4,22 @@ module Segment module Analytics describe Client do describe '#init' do - it 'should error if no secret is supplied' do + it 'should error if no write_key is supplied' do expect { Client.new }.to raise_error(RuntimeError) end - it 'should not error if a secret is supplied' do - Client.new :secret => SECRET + it 'should not error if a write_key is supplied' do + Client.new :write_key => WRITE_KEY end - it 'should not error if a secret is supplied as a string' do - Client.new 'secret' => SECRET + it 'should not error if a write_key is supplied as a string' do + Client.new 'write_key' => WRITE_KEY end end describe '#track' do before(:all) do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end @@ -71,7 +71,7 @@ module Analytics describe '#identify' do before(:all) do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end @@ -106,7 +106,7 @@ module Analytics describe '#alias' do before :all do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY end it 'should error without from' do @@ -128,7 +128,7 @@ module Analytics describe '#group' do before :all do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY end it 'should error without group_id' do @@ -150,7 +150,7 @@ module Analytics describe '#page' do before :all do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY end it 'should error without user_id' do @@ -172,7 +172,7 @@ module Analytics describe '#screen' do before :all do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY end it 'should error without user_id' do @@ -194,7 +194,7 @@ module Analytics describe '#flush' do before(:all) do - @client = Client.new :secret => SECRET + @client = Client.new :write_key => WRITE_KEY end it 'should wait for the queue to finish on a flush' do diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index f1a75b3..469b8a3 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -21,9 +21,8 @@ module Analytics end describe '#init' do - it 'should successfully init' do - Segment::Analytics.init :secret => SECRET + Segment::Analytics.init :write_key => WRITE_KEY end end @@ -125,7 +124,7 @@ module Analytics describe "#initialized?" do context "when initialized" do it "should return true" do - Segment::Analytics.init :secret => SECRET + Segment::Analytics.init :write_key => WRITE_KEY expect(Segment::Analytics.initialized?).to be_true end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 592a019..009c0bd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ module Segment module Analytics - SECRET = 'testsecret' + WRITE_KEY = 'testsecret' TRACK = { :event => 'Ruby Library test event', From dffbf1ac1000c436087e1c53871c8e277b8b3934 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 12:50:33 -0500 Subject: [PATCH 034/322] Use basic auth (fix #46) --- lib/segment/analytics/request.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 1d5d0cb..5f0d0ca 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -39,13 +39,14 @@ def post(write_key, batch) backoff = @backoff headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin - payload = JSON.generate :writeKey => write_key, :batch => batch + payload = JSON.generate :batch => batch request = Net::HTTP::Post.new(@path, headers) + request.basic_auth write_key if self.class.stub status = 200 error = nil - logger.debug "stubbed request to #{@path} with payload #{payload}" + logger.debug "stubbed request to #{@path}: write key = #{write_key}, payload = #{payload}" else res = @http.request(request, payload) status = res.code.to_i From 40495cbd7d5d6807232a440502a56c0144928650 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 12:57:59 -0500 Subject: [PATCH 035/322] Add backtrace to logs --- lib/segment/analytics/request.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 5f0d0ca..5f657be 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -54,10 +54,11 @@ def post(write_key, batch) error = body["error"] end - rescue Exception => err - logger.error err.message + rescue Exception => e + logger.error e.message + e.backtrace.each { |line| logger.error line } status = -1 - error = "Connection error: #{err}" + error = "Connection error: #{e}" logger.info "retries remaining: #{remaining_retries}" unless (remaining_retries -=1).zero? From 82d57c435f114b935f1b51c0a10561a29f6fe977 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 12:59:11 -0500 Subject: [PATCH 036/322] Fix basic auth call --- lib/segment/analytics/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 5f657be..2530730 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -41,7 +41,7 @@ def post(write_key, batch) begin payload = JSON.generate :batch => batch request = Net::HTTP::Post.new(@path, headers) - request.basic_auth write_key + request.basic_auth write_key, nil if self.class.stub status = 200 From 42db83a19279e211654dcdafc0e317749cccdc5a Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 13:04:10 -0500 Subject: [PATCH 037/322] Fix library to be an object (#44) --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index e5c6ab7..14157ec 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -291,7 +291,7 @@ def ensure_user(user_id) # # context - Hash of call context def add_context(context) - context[:library] = 'analytics-ruby' + context[:library] = { name: "analytics-ruby", version: Segment::Analytics::VERSION.to_s } end # private: Checks that the write_key is properly initialized From 85d8653f42a9a2314c2de3ae955e9d3954a9a3c2 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 16:08:36 -0500 Subject: [PATCH 038/322] Add ios8601 methods for dates, times --- lib/segment/analytics/utils.rb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index d02a150..1205640 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -26,7 +26,13 @@ def stringify_keys(hash) # def isoify_dates(hash) hash.inject({}) { |memo, (k, v)| - memo[k] = v.respond_to?(:iso8601) ? v.iso8601 : v + memo[k] = if v.is_a? Date + date_in_iso8601 v + elsif v.is_a? Time + time_in_iso8601 v + else + v + end memo } end @@ -42,6 +48,18 @@ def isoify_dates!(hash) def uid (0..16).to_a.map{|x| rand(16).to_s(16)}.join end + + def time_in_iso8601 time, fraction_digits = 0 + fraction = if fraction_digits > 0 + (".%06i" % time.usec)[0, fraction_digits + 1] + end + + "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" + end + + def date_in_iso8601 date + date.strftime("%F") + end end end end From ff870d0dff659d775271849d6232813a40affdd0 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 17:22:31 -0500 Subject: [PATCH 039/322] Fix assuming we're in Rails without having ActiveSupport as a dependency --- lib/segment/analytics/client.rb | 23 ++++++++++++++++++----- lib/segment/analytics/utils.rb | 31 +++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 14157ec..bf9f6fb 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -83,7 +83,7 @@ def track options :userId => user_id, :context => context, :properties => properties, - :timestamp => timestamp.iso8601, + :timestamp => datetime_in_iso8601(timestamp), :action => 'track' }) end @@ -117,7 +117,7 @@ def identify options :userId => user_id, :context => context, :traits => traits, - :timestamp => timestamp.iso8601, + :timestamp => datetime_in_iso8601(timestamp), :action => 'identify' }) end @@ -147,7 +147,7 @@ def alias(options) :from => from, :to => to, :context => context, - :timestamp => timestamp.iso8601, + :timestamp => datetime_in_iso8601(timestamp), :action => 'alias' }) end @@ -181,7 +181,7 @@ def group(options) :userId => user_id, :traits => traits, :context => context, - :timestamp => timestamp.iso8601, + :timestamp => datetime_in_iso8601(timestamp), :action => 'group' }) end @@ -217,7 +217,7 @@ def page(options) :name => name, :properties => properties, :context => context, - :timestamp => timestamp.iso8601, + :timestamp => datetime_in_iso8601(timestamp), :action => 'page' }) end @@ -304,6 +304,19 @@ def check_timestamp(timestamp) fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time end + def event attrs + symbolize_keys! attrs + + { + :userId => user_id, + :name => name, + :properties => properties, + :context => context, + :timestamp => datetime_in_iso8601(timestamp), + :action => 'screen' + } + end + # Sub-class thread so we have a named thread (useful for debugging in Thread.list). class ConsumerThread < Thread end diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 1205640..ca41acf 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -26,13 +26,7 @@ def stringify_keys(hash) # def isoify_dates(hash) hash.inject({}) { |memo, (k, v)| - memo[k] = if v.is_a? Date - date_in_iso8601 v - elsif v.is_a? Time - time_in_iso8601 v - else - v - end + memo[k] = datetime_in_iso8601(v) memo } end @@ -49,17 +43,38 @@ def uid (0..16).to_a.map{|x| rand(16).to_s(16)}.join end + def datetime_in_iso8601 datetime + if datetime.is_a? Date + date_in_iso8601 datetime + elsif datetime.is_a? Time + time_in_iso8601 datetime + else + datetime + end + end + def time_in_iso8601 time, fraction_digits = 0 fraction = if fraction_digits > 0 (".%06i" % time.usec)[0, fraction_digits + 1] end - "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" + "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(time, true, 'Z')}" end def date_in_iso8601 date date.strftime("%F") end + + def formatted_offset time, colon = true, alternate_utc_string = nil + time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon) + end + + def seconds_to_utc_offset(seconds, colon = true) + (colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), seconds.abs, (seconds.abs % 3600)] + end + + UTC_OFFSET_WITH_COLON = '%s%02d:%02d' + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '') end end end From 12fc0fd5367e085eadc03b27b695a7b1c40b58f8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 17:22:47 -0500 Subject: [PATCH 040/322] Add sentAt to request --- lib/segment/analytics/request.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 2530730..ffe18ca 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -1,4 +1,5 @@ require 'segment/analytics/defaults' +require 'segment/analytics/utils' require 'segment/analytics/response' require 'segment/analytics/logging' require 'net/http' @@ -9,6 +10,7 @@ module Segment module Analytics class Request include Segment::Analytics::Defaults::Request + include Segment::Analytics::Utils include Segment::Analytics::Logging # public: Creates a new request object to send analytics batch @@ -39,7 +41,7 @@ def post(write_key, batch) backoff = @backoff headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin - payload = JSON.generate :batch => batch + payload = JSON.generate :sentAt => datetime_in_iso8601(Time.new), :batch => batch request = Net::HTTP::Post.new(@path, headers) request.basic_auth write_key, nil From 8a5f3bcd8a7e4bc9a81494f504644687f950efae Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 18:03:28 -0500 Subject: [PATCH 041/322] Fix action -> type (#44) --- lib/segment/analytics/client.rb | 14 +++++++------- spec/spec_helper.rb | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index bf9f6fb..969d524 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -84,7 +84,7 @@ def track options :context => context, :properties => properties, :timestamp => datetime_in_iso8601(timestamp), - :action => 'track' + :type => 'track' }) end @@ -118,7 +118,7 @@ def identify options :context => context, :traits => traits, :timestamp => datetime_in_iso8601(timestamp), - :action => 'identify' + :type => 'identify' }) end @@ -148,7 +148,7 @@ def alias(options) :to => to, :context => context, :timestamp => datetime_in_iso8601(timestamp), - :action => 'alias' + :type => 'alias' }) end @@ -182,7 +182,7 @@ def group(options) :traits => traits, :context => context, :timestamp => datetime_in_iso8601(timestamp), - :action => 'group' + :type => 'group' }) end @@ -218,7 +218,7 @@ def page(options) :properties => properties, :context => context, :timestamp => datetime_in_iso8601(timestamp), - :action => 'page' + :type => 'page' }) end # public: Records a screen view (for a mobile app) @@ -253,7 +253,7 @@ def screen(options) :properties => properties, :context => context, :timestamp => timestamp.iso8601, - :action => 'screen' + :type => 'screen' }) end @@ -313,7 +313,7 @@ def event attrs :properties => properties, :context => context, :timestamp => datetime_in_iso8601(timestamp), - :action => 'screen' + :type => 'screen' } end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 009c0bd..fb83c39 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -53,18 +53,18 @@ module Queued module Requested TRACK = TRACK.merge({ :userId => USER_ID, - :action => 'track' + :type => 'track' }) IDENTIFY = IDENTIFY.merge({ :userId => USER_ID, - :action => 'identify' + :type => 'identify' }) GROUP = GROUP.merge({ :groupId => GROUP_ID, :userId => USER_ID, - :action => 'group' + :type => 'group' }) PAGE = PAGE.merge :userId => USER_ID From 6d1f1e31b59afe5b2a98996b5a4512a8e3651113 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 18:07:30 -0500 Subject: [PATCH 042/322] Fix from -> previousId (#44) --- lib/segment/analytics/client.rb | 8 ++++---- spec/segment/analytics/client_spec.rb | 2 +- spec/segment/analytics_spec.rb | 2 +- spec/spec_helper.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 969d524..35b5228 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -125,7 +125,7 @@ def identify options # public: Aliases a user from one id to another # # options - Hash - # :from - String of the id to alias from + # :previousId - String of the id to alias from # :to - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) @@ -133,7 +133,7 @@ def alias(options) check_write_key symbolize_keys! options - from = options[:from].to_s + from = options[:previousId].to_s to = options[:to].to_s timestamp = options[:timestamp] || Time.new context = options[:context] || {} @@ -144,7 +144,7 @@ def alias(options) add_context context enqueue({ - :from => from, + :previousId => from, :to => to, :context => context, :timestamp => datetime_in_iso8601(timestamp), @@ -155,7 +155,7 @@ def alias(options) # public: Associates a user identity with a group. # # options - Hash - # :from - String of the id to alias from + # :previousId - String of the id to alias from # :to - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index e34f1db..93e0520 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -114,7 +114,7 @@ module Analytics end it 'should error without to' do - expect { @client.alias :from => 1234 }.to raise_error(ArgumentError) + expect { @client.alias :previousId => 1234 }.to raise_error(ArgumentError) end it 'should not error with the required options' do diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index 469b8a3..b5e48f1 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -60,7 +60,7 @@ module Analytics end it 'should error without to' do - expect { Segment::Analytics.alias :from => 1234 }.to raise_error(ArgumentError) + expect { Segment::Analytics.alias :previousId => 1234 }.to raise_error(ArgumentError) end it 'should not error with the required options' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fb83c39..7e9b462 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,7 +23,7 @@ module Analytics } ALIAS = { - :from => 1234, + :previousId => 1234, :to => 'abcd' } From b539568ca8574f6f91a7c2c727f1d381a61493ff Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 18:12:07 -0500 Subject: [PATCH 043/322] Remove new hash syntax --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 35b5228..1b73837 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -291,7 +291,7 @@ def ensure_user(user_id) # # context - Hash of call context def add_context(context) - context[:library] = { name: "analytics-ruby", version: Segment::Analytics::VERSION.to_s } + context[:library] = { :name => "analytics-ruby", :version => Segment::Analytics::VERSION.to_s } end # private: Checks that the write_key is properly initialized From 5804afe7b25734d2986e18f9761916ae54f2c362 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 18:13:41 -0500 Subject: [PATCH 044/322] Fix to -> userId (#44) --- lib/segment/analytics/client.rb | 8 ++++---- spec/segment/analytics/client_spec.rb | 2 +- spec/segment/analytics_spec.rb | 4 ++-- spec/spec_helper.rb | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 1b73837..731668c 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -126,7 +126,7 @@ def identify options # # options - Hash # :previousId - String of the id to alias from - # :to - String of the id to alias to + # :userId - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def alias(options) @@ -134,7 +134,7 @@ def alias(options) symbolize_keys! options from = options[:previousId].to_s - to = options[:to].to_s + to = options[:userId].to_s timestamp = options[:timestamp] || Time.new context = options[:context] || {} @@ -145,7 +145,7 @@ def alias(options) enqueue({ :previousId => from, - :to => to, + :userId => to, :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'alias' @@ -156,7 +156,7 @@ def alias(options) # # options - Hash # :previousId - String of the id to alias from - # :to - String of the id to alias to + # :userId - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def group(options) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 93e0520..cb55698 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -110,7 +110,7 @@ module Analytics end it 'should error without from' do - expect { @client.alias :to => 1234 }.to raise_error(ArgumentError) + expect { @client.alias :userId => 1234 }.to raise_error(ArgumentError) end it 'should error without to' do diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index b5e48f1..a71cb42 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -22,7 +22,7 @@ module Analytics describe '#init' do it 'should successfully init' do - Segment::Analytics.init :write_key => WRITE_KEY + Segment::Analytics.init :write_key => WRITE_KEY, :stub => true end end @@ -56,7 +56,7 @@ module Analytics describe '#alias' do it 'should error without from' do - expect { Segment::Analytics.alias :to => 1234 }.to raise_error(ArgumentError) + expect { Segment::Analytics.alias :userId => 1234 }.to raise_error(ArgumentError) end it 'should error without to' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7e9b462..e786a06 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,7 +24,7 @@ module Analytics ALIAS = { :previousId => 1234, - :to => 'abcd' + :userId => 'abcd' } GROUP = {} From db1b7ceb3fca671288ce5e51df671b03b8ab3900 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 20:03:00 -0500 Subject: [PATCH 045/322] Fix analytics to be an instance --- lib/segment/analytics.rb | 29 +--- lib/segment/analytics/client.rb | 2 +- lib/segment/analytics/consumer.rb | 2 +- lib/segment/analytics/defaults.rb | 2 +- lib/segment/analytics/logging.rb | 2 +- lib/segment/analytics/request.rb | 2 +- lib/segment/analytics/response.rb | 2 +- lib/segment/analytics/utils.rb | 2 +- lib/segment/analytics/version.rb | 2 +- spec/segment/analytics/client_spec.rb | 2 +- spec/segment/analytics/consumer_spec.rb | 2 +- spec/segment/analytics_spec.rb | 171 ++++++++++-------------- spec/spec_helper.rb | 2 +- 13 files changed, 87 insertions(+), 135 deletions(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 2d8a187..3582850 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -8,32 +8,15 @@ require 'segment/analytics/logging' module Segment - module Analytics - extend self - - def setup options = {} - @options = options - Request.stub = @options[:stub] - end - alias_method :init, :setup - - def setup? - !!@options - end - alias_method :"initialized?", :"setup?" - - def client - @client ||= Segment::Analytics::Client.new @options + class Analytics + def initialize options = {} + Request.stub = options[:stub] + @client = Segment::Analytics::Client.new options end def method_missing message, *args, &block - if Segment::Analytics::Client.method_defined? message - if setup? - client.send message, *args, &block - else - logger.warn "messaged ##{message} before #setup" - nil - end + if @client.respond_to? message + @client.send message, *args, &block else super end diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 731668c..f249c2c 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -5,7 +5,7 @@ require 'segment/analytics/defaults' module Segment - module Analytics + class Analytics class Client include Segment::Analytics::Utils diff --git a/lib/segment/analytics/consumer.rb b/lib/segment/analytics/consumer.rb index 43b1ca6..aa1478d 100644 --- a/lib/segment/analytics/consumer.rb +++ b/lib/segment/analytics/consumer.rb @@ -4,7 +4,7 @@ require 'segment/analytics/request' module Segment - module Analytics + class Analytics class Consumer include Segment::Analytics::Utils include Segment::Analytics::Defaults diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index b5a9b56..aeb933e 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -1,5 +1,5 @@ module Segment - module Analytics + class Analytics module Defaults module Request HOST = 'api.segment.io' diff --git a/lib/segment/analytics/logging.rb b/lib/segment/analytics/logging.rb index 2b7eaca..e7f5229 100644 --- a/lib/segment/analytics/logging.rb +++ b/lib/segment/analytics/logging.rb @@ -1,7 +1,7 @@ require 'logger' module Segment - module Analytics + class Analytics module Logging class << self def logger diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index ffe18ca..a19897d 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -7,7 +7,7 @@ require 'json' module Segment - module Analytics + class Analytics class Request include Segment::Analytics::Defaults::Request include Segment::Analytics::Utils diff --git a/lib/segment/analytics/response.rb b/lib/segment/analytics/response.rb index 844baed..ebee226 100644 --- a/lib/segment/analytics/response.rb +++ b/lib/segment/analytics/response.rb @@ -1,5 +1,5 @@ module Segment - module Analytics + class Analytics class Response attr_reader :status, :error diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index ca41acf..9ec6f48 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -1,5 +1,5 @@ module Segment - module Analytics + class Analytics module Utils extend self diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 3585cba..bacc4b2 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment - module Analytics + class Analytics VERSION = '1.1.0' end end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index cb55698..2b4b393 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Segment - module Analytics + class Analytics describe Client do describe '#init' do it 'should error if no write_key is supplied' do diff --git a/spec/segment/analytics/consumer_spec.rb b/spec/segment/analytics/consumer_spec.rb index 33276c3..65fc34f 100644 --- a/spec/segment/analytics/consumer_spec.rb +++ b/spec/segment/analytics/consumer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module Segment - module Analytics + class Analytics describe Consumer do describe "#init" do it 'accepts string keys' do diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index a71cb42..8c3a053 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -1,134 +1,103 @@ require 'spec_helper' module Segment - module Analytics - describe '#not-initialized' do - it 'should ignore calls to track if not initialized' do - expect { Segment::Analytics.track({}) }.not_to raise_error - end - - it 'should return nil on track if not initialized' do - Segment::Analytics.track({}).should be_nil - end + class Analytics + describe Analytics do + let(:analytics) { Segment::Analytics.new :write_key => WRITE_KEY, :stub => true } - it 'should ignore calls to identify if not initialized' do - expect { Segment::Analytics.identify({}) }.not_to raise_error - end - - it 'should return nil on identify if not initialized' do - Segment::Analytics.identify({}).should be_nil - end - end - - describe '#init' do - it 'should successfully init' do - Segment::Analytics.init :write_key => WRITE_KEY, :stub => true - end - end - - describe '#track' do - - it 'should error without an event' do - expect { Segment::Analytics.track :user_id => 'user' }.to raise_error(ArgumentError) - end + describe '#track' do + it 'should error without an event' do + expect { analytics.track(:user_id => 'user') }.to raise_error(ArgumentError) + end - it 'should error without a user_id' do - expect { Segment::Analytics.track :event => 'Event' }.to raise_error(ArgumentError) - end + it 'should error without a user_id' do + expect { analytics.track(:event => 'Event') }.to raise_error(ArgumentError) + end - it 'should not error with the required options' do - Segment::Analytics.track Queued::TRACK - sleep(1) + it 'should not error with the required options' do + analytics.track Queued::TRACK + sleep(1) + end end - end - - describe '#identify' do - it 'should error without a user_id' do - expect { Segment::Analytics.identify :traits => {} }.to raise_error(ArgumentError) - end - it 'should not error with the required options' do - Segment::Analytics.identify Queued::IDENTIFY - sleep(1) - end - end + describe '#identify' do + it 'should error without a user_id' do + expect { analytics.identify :traits => {} }.to raise_error(ArgumentError) + end - describe '#alias' do - it 'should error without from' do - expect { Segment::Analytics.alias :userId => 1234 }.to raise_error(ArgumentError) + it 'should not error with the required options' do + analytics.identify Queued::IDENTIFY + sleep(1) + end end - it 'should error without to' do - expect { Segment::Analytics.alias :previousId => 1234 }.to raise_error(ArgumentError) - end + describe '#alias' do + it 'should error without from' do + expect { analytics.alias :userId => 1234 }.to raise_error(ArgumentError) + end - it 'should not error with the required options' do - Segment::Analytics.alias ALIAS - sleep(1) - end - end + it 'should error without to' do + expect { analytics.alias :previousId => 1234 }.to raise_error(ArgumentError) + end - describe '#group' do - it 'should error without group_id' do - expect { Segment::Analytics.group :user_id => 'foo' }.to raise_error(ArgumentError) + it 'should not error with the required options' do + analytics.alias ALIAS + sleep(1) + end end - it 'should error without user_id' do - expect { Segment::Analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) - end + describe '#group' do + it 'should error without group_id' do + expect { analytics.group :user_id => 'foo' }.to raise_error(ArgumentError) + end - it 'should not error with the required options' do - Segment::Analytics.group Queued::GROUP - sleep(1) - end - end + it 'should error without user_id' do + expect { analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) + end - describe '#page' do - it 'should error without user_id' do - expect { Segment::Analytics.page :name => 'foo' }.to raise_error(ArgumentError) + it 'should not error with the required options' do + analytics.group Queued::GROUP + sleep(1) + end end - it 'should error without name' do - expect { Segment::Analytics.page :user_id => 1 }.to raise_error(ArgumentError) - end + describe '#page' do + it 'should error without user_id' do + expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError) + end - it 'should not error with the required options' do - Segment::Analytics.page Queued::PAGE - sleep(1) - end - end + it 'should error without name' do + expect { analytics.page :user_id => 1 }.to raise_error(ArgumentError) + end - describe '#screen' do - it 'should error without user_id' do - expect { Segment::Analytics.screen :name => 'foo' }.to raise_error(ArgumentError) + it 'should not error with the required options' do + analytics.page Queued::PAGE + sleep(1) + end end - it 'should error without name' do - expect { Segment::Analytics.screen :user_id => 1 }.to raise_error(ArgumentError) - end + describe '#screen' do + it 'should error without user_id' do + expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) + end - it 'should not error with the required options' do - Segment::Analytics.screen Queued::SCREEN - sleep(1) - end - end + it 'should error without name' do + expect { analytics.screen :user_id => 1 }.to raise_error(ArgumentError) + end - describe '#flush' do - it 'should flush without error' do - Segment::Analytics.identify Queued::IDENTIFY - Segment::Analytics.flush + it 'should not error with the required options' do + analytics.screen Queued::SCREEN + sleep(1) + end end - end - describe "#initialized?" do - context "when initialized" do - it "should return true" do - Segment::Analytics.init :write_key => WRITE_KEY - expect(Segment::Analytics.initialized?).to be_true + describe '#flush' do + it 'should flush without error' do + analytics.identify Queued::IDENTIFY + analytics.flush end end end end end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e786a06..5f8b093 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ require 'segment/analytics' module Segment - module Analytics + class Analytics WRITE_KEY = 'testsecret' TRACK = { From 0cb8fd5fe03f7c50f18ece30a1bdc743ce2cf2d0 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 21:05:44 -0500 Subject: [PATCH 046/322] Fix requestId -> messageId (#44) --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index f249c2c..21804b1 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -271,7 +271,7 @@ def queued_messages # returns Boolean of whether the item was added to the queue. def enqueue(action) # add our request id for tracing purposes - action[:requestId] = uid + action[:messageId] = uid queue_full = @queue.length >= @max_queue_size @queue << action unless queue_full From d1c86ccba4d2d4c1e0b1da0b83e5bbc27df6268b Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 9 May 2014 22:21:35 -0500 Subject: [PATCH 047/322] Add respond_to? --- lib/segment/analytics.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 3582850..b95d06a 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -22,6 +22,10 @@ def method_missing message, *args, &block end end + def respond_to? method_name, include_private = false + @client.respond_to?(method_missing) || super + end + include Logging end end From 27450e5c4810f6548793f283c007a14e64a9c40d Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 13:15:00 -0500 Subject: [PATCH 048/322] Dont set Analytics, so people can set their instance to that --- lib/segment/analytics.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index b95d06a..d51b34e 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -18,7 +18,7 @@ def method_missing message, *args, &block if @client.respond_to? message @client.send message, *args, &block else - super + super end end @@ -29,6 +29,3 @@ def respond_to? method_name, include_private = false include Logging end end - -Analytics = Segment::Analytics unless defined? Analytics - From 4eb91ec2fcf32d30cc3d6d9046fcbd7430f4e1ce Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 19:55:42 -0500 Subject: [PATCH 049/322] Update version to 2.0.0 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index bacc4b2..8fa12d6 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,6 +1,6 @@ module Segment class Analytics - VERSION = '1.1.0' + VERSION = '2.0.0' end end From 4fe35cb2c538b724ef3d5205c2179507806c1077 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 21:03:13 -0500 Subject: [PATCH 050/322] Check write key once in initializer (#49) --- lib/segment/analytics/client.rb | 35 +++++++++------------------ spec/segment/analytics/client_spec.rb | 4 +-- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 21804b1..9bee621 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -21,12 +21,11 @@ def initialize options = {} @queue = Queue.new @write_key = options[:write_key] @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE - - check_write_key - @consumer = Consumer.new @queue, @write_key, options @thread = ConsumerThread.new { @consumer.run } + check_write_key! + at_exit do # Let the consumer thread know it should exit. @thread[:should_exit] = true @@ -56,8 +55,6 @@ def flush # :timestamp - Time of when the event occurred. (optional) # :context - Hash of context. (optional) def track options - check_write_key - symbolize_keys! options event = options[:event] @@ -67,7 +64,7 @@ def track options context = options[:context] || {} ensure_user user_id - check_timestamp timestamp + check_timestamp! timestamp if event.nil? || event.empty? fail ArgumentError, 'Must supply event as a non-empty string' @@ -96,8 +93,6 @@ def track options # :timestamp - Time of when the event occurred. (optional) # :context - Hash of context. (optional) def identify options - check_write_key - symbolize_keys! options user_id = options[:user_id].to_s @@ -106,7 +101,7 @@ def identify options context = options[:context] || {} ensure_user user_id - check_timestamp timestamp + check_timestamp! timestamp fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash isoify_dates! traits @@ -130,8 +125,6 @@ def identify options # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def alias(options) - check_write_key - symbolize_keys! options from = options[:previousId].to_s to = options[:userId].to_s @@ -140,7 +133,7 @@ def alias(options) ensure_user from ensure_user to - check_timestamp timestamp + check_timestamp! timestamp add_context context enqueue({ @@ -160,8 +153,6 @@ def alias(options) # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def group(options) - check_write_key - symbolize_keys! options group_id = options[:group_id].to_s user_id = options[:user_id].to_s @@ -173,7 +164,7 @@ def group(options) ensure_user group_id ensure_user user_id - check_timestamp timestamp + check_timestamp! timestamp add_context context enqueue({ @@ -195,8 +186,6 @@ def group(options) # :timestamp - Time of when the pageview occured (optional) # :context - Hash of context (optional) def page(options) - check_write_key - symbolize_keys! options user_id = options[:user_id].to_s name = options[:name].to_s @@ -209,7 +198,7 @@ def page(options) isoify_dates! properties ensure_user user_id - check_timestamp timestamp + check_timestamp! timestamp add_context context enqueue({ @@ -230,8 +219,6 @@ def page(options) # :timestamp - Time of when the screen occured (optional) # :context - Hash of context (optional) def screen(options) - check_write_key - symbolize_keys! options user_id = options[:user_id].to_s name = options[:name].to_s @@ -244,7 +231,7 @@ def screen(options) isoify_dates! properties ensure_user user_id - check_timestamp timestamp + check_timestamp! timestamp add_context context enqueue({ @@ -295,12 +282,12 @@ def add_context(context) end # private: Checks that the write_key is properly initialized - def check_write_key - fail 'Write key must be initialized' if @write_key.nil? + def check_write_key! + fail ArgumentError, 'Write key must be initialized' if @write_key.nil? end # private: Checks the timstamp option to make sure it is a Time. - def check_timestamp(timestamp) + def check_timestamp!(timestamp) fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 2b4b393..1d7a14d 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -3,9 +3,9 @@ module Segment class Analytics describe Client do - describe '#init' do + describe '#initialize' do it 'should error if no write_key is supplied' do - expect { Client.new }.to raise_error(RuntimeError) + expect { Client.new }.to raise_error(ArgumentError) end it 'should not error if a write_key is supplied' do From 249cbb272fc954a42ca901ea1e2bf2277bc05d91 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 21:40:54 -0500 Subject: [PATCH 051/322] Check whether anonymous id or user id is given (#49) --- lib/segment/analytics/client.rb | 42 ++++++++++++++++++++------------- spec/segment/analytics_spec.rb | 6 ++--- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 9bee621..0ebed32 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -56,14 +56,13 @@ def flush # :context - Hash of context. (optional) def track options symbolize_keys! options + validate_options! options event = options[:event] - user_id = options[:user_id].to_s properties = options[:properties] || {} timestamp = options[:timestamp] || Time.new context = options[:context] || {} - ensure_user user_id check_timestamp! timestamp if event.nil? || event.empty? @@ -77,7 +76,8 @@ def track options enqueue({ :event => event, - :userId => user_id, + :userId => options[:user_id].to_s, + :anonymousId => options[:anonymous_id].to_s, :context => context, :properties => properties, :timestamp => datetime_in_iso8601(timestamp), @@ -94,13 +94,12 @@ def track options # :context - Hash of context. (optional) def identify options symbolize_keys! options + validate_options! options - user_id = options[:user_id].to_s traits = options[:traits] || {} timestamp = options[:timestamp] || Time.new context = options[:context] || {} - ensure_user user_id check_timestamp! timestamp fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash @@ -109,7 +108,8 @@ def identify options add_context context enqueue({ - :userId => user_id, + :userId => options[:user_id].to_s, + :anonymousId => options[:anonymous_id].to_s, :context => context, :traits => traits, :timestamp => datetime_in_iso8601(timestamp), @@ -126,13 +126,14 @@ def identify options # :context - Hash of context (optional) def alias(options) symbolize_keys! options + from = options[:previousId].to_s to = options[:userId].to_s timestamp = options[:timestamp] || Time.new context = options[:context] || {} - ensure_user from - ensure_user to + check_user! from + check_user! to check_timestamp! timestamp add_context context @@ -154,6 +155,8 @@ def alias(options) # :context - Hash of context (optional) def group(options) symbolize_keys! options + validate_options! options + group_id = options[:group_id].to_s user_id = options[:user_id].to_s traits = options[:traits] || {} @@ -162,8 +165,7 @@ def group(options) fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash - ensure_user group_id - ensure_user user_id + check_user! group_id check_timestamp! timestamp add_context context @@ -187,7 +189,8 @@ def group(options) # :context - Hash of context (optional) def page(options) symbolize_keys! options - user_id = options[:user_id].to_s + validate_options! options + name = options[:name].to_s properties = options[:properties] || {} timestamp = options[:timestamp] || Time.new @@ -197,12 +200,12 @@ def page(options) fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties - ensure_user user_id check_timestamp! timestamp add_context context enqueue({ - :userId => user_id, + :userId => options[:user_id].to_s, + :anonymousId => options[:anonymous_id].to_s, :name => name, :properties => properties, :context => context, @@ -220,7 +223,8 @@ def page(options) # :context - Hash of context (optional) def screen(options) symbolize_keys! options - user_id = options[:user_id].to_s + validate_options! options + name = options[:name].to_s properties = options[:properties] || {} timestamp = options[:timestamp] || Time.new @@ -230,12 +234,12 @@ def screen(options) fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties - ensure_user user_id check_timestamp! timestamp add_context context enqueue({ - :userId => user_id, + :userId => options[:user_id].to_s, + :anonymousId => options[:anonymous_id].to_s, :name => name, :properties => properties, :context => context, @@ -270,7 +274,7 @@ def enqueue(action) # # user_id - String of the user id # - def ensure_user(user_id) + def check_user!(user_id) fail ArgumentError, 'Must supply a non-empty user_id' if user_id.empty? end @@ -304,6 +308,10 @@ def event attrs } end + def validate_options! options + fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id] + end + # Sub-class thread so we have a named thread (useful for debugging in Thread.list). class ConsumerThread < Thread end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index 8c3a053..a0494db 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -52,7 +52,7 @@ class Analytics expect { analytics.group :user_id => 'foo' }.to raise_error(ArgumentError) end - it 'should error without user_id' do + it 'should error without user_id or anonymous_id' do expect { analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) end @@ -63,7 +63,7 @@ class Analytics end describe '#page' do - it 'should error without user_id' do + it 'should error without user_id or anonymous_id' do expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError) end @@ -78,7 +78,7 @@ class Analytics end describe '#screen' do - it 'should error without user_id' do + it 'should error without user_id or anonymous_id' do expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) end From fd973472f17a34023b34a660e221ebab46e4b9d8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 21:45:39 -0500 Subject: [PATCH 052/322] Rename method --- lib/segment/analytics/client.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 0ebed32..3797a28 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -56,7 +56,7 @@ def flush # :context - Hash of context. (optional) def track options symbolize_keys! options - validate_options! options + check_user_id! options event = options[:event] properties = options[:properties] || {} @@ -94,7 +94,7 @@ def track options # :context - Hash of context. (optional) def identify options symbolize_keys! options - validate_options! options + check_user_id! options traits = options[:traits] || {} timestamp = options[:timestamp] || Time.new @@ -155,7 +155,7 @@ def alias(options) # :context - Hash of context (optional) def group(options) symbolize_keys! options - validate_options! options + check_user_id! options group_id = options[:group_id].to_s user_id = options[:user_id].to_s @@ -189,7 +189,7 @@ def group(options) # :context - Hash of context (optional) def page(options) symbolize_keys! options - validate_options! options + check_user_id! options name = options[:name].to_s properties = options[:properties] || {} @@ -223,7 +223,7 @@ def page(options) # :context - Hash of context (optional) def screen(options) symbolize_keys! options - validate_options! options + check_user_id! options name = options[:name].to_s properties = options[:properties] || {} @@ -308,7 +308,7 @@ def event attrs } end - def validate_options! options + def check_user_id! options fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id] end From dc7c6fe6d3b0f5dc574ec2c0da35f66bc21db525 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 12 May 2014 22:23:02 -0500 Subject: [PATCH 053/322] Fix id checks (#49) --- lib/segment/analytics/client.rb | 27 +++++++++++++------------ spec/segment/analytics/client_spec.rb | 4 ++-- spec/segment/analytics/consumer_spec.rb | 3 +-- spec/segment/analytics_spec.rb | 4 ++-- spec/spec_helper.rb | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 3797a28..3309c15 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -120,20 +120,20 @@ def identify options # public: Aliases a user from one id to another # # options - Hash - # :previousId - String of the id to alias from - # :userId - String of the id to alias to + # :previous_id - String of the id to alias from + # :user_id - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def alias(options) symbolize_keys! options - from = options[:previousId].to_s - to = options[:userId].to_s + from = options[:previous_id].to_s + to = options[:user_id].to_s timestamp = options[:timestamp] || Time.new context = options[:context] || {} - check_user! from - check_user! to + check_non_empty_string! from, 'previous_id' + check_non_empty_string! to, 'user_id' check_timestamp! timestamp add_context context @@ -149,8 +149,8 @@ def alias(options) # public: Associates a user identity with a group. # # options - Hash - # :previousId - String of the id to alias from - # :userId - String of the id to alias to + # :previous_id - String of the id to alias from + # :user_id - String of the id to alias to # :timestamp - Time of when the alias occured (optional) # :context - Hash of context (optional) def group(options) @@ -165,7 +165,7 @@ def group(options) fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash - check_user! group_id + check_non_empty_string! group_id, 'group_id' check_timestamp! timestamp add_context context @@ -270,12 +270,13 @@ def enqueue(action) !queue_full end - # private: Ensures that a user id was passed in. + # private: Ensures that a string is non-empty # - # user_id - String of the user id + # str - String that must be non-empty + # name - Name of the validated value # - def check_user!(user_id) - fail ArgumentError, 'Must supply a non-empty user_id' if user_id.empty? + def check_non_empty_string!(str, name) + fail ArgumentError, "Must supply a non-empty #{name}" if str.empty? end # private: Adds contextual information to the call diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 1d7a14d..aa96def 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -110,11 +110,11 @@ class Analytics end it 'should error without from' do - expect { @client.alias :userId => 1234 }.to raise_error(ArgumentError) + expect { @client.alias :user_id => 1234 }.to raise_error(ArgumentError) end it 'should error without to' do - expect { @client.alias :previousId => 1234 }.to raise_error(ArgumentError) + expect { @client.alias :previous_id => 1234 }.to raise_error(ArgumentError) end it 'should not error with the required options' do diff --git a/spec/segment/analytics/consumer_spec.rb b/spec/segment/analytics/consumer_spec.rb index 65fc34f..f4cbf0a 100644 --- a/spec/segment/analytics/consumer_spec.rb +++ b/spec/segment/analytics/consumer_spec.rb @@ -34,8 +34,7 @@ class Analytics end it 'should execute the error handler if the request is invalid' do - Segment::Analytics::Request.any_instance.stub(:post).and_return( - Segment::Analytics::Response.new(400, "Some error")) + Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, "Some error")) on_error = Proc.new do |status, error| puts "#{status}, #{error}" diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index a0494db..ef47968 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -34,11 +34,11 @@ class Analytics describe '#alias' do it 'should error without from' do - expect { analytics.alias :userId => 1234 }.to raise_error(ArgumentError) + expect { analytics.alias :user_id => 1234 }.to raise_error(ArgumentError) end it 'should error without to' do - expect { analytics.alias :previousId => 1234 }.to raise_error(ArgumentError) + expect { analytics.alias :previous_id => 1234 }.to raise_error(ArgumentError) end it 'should not error with the required options' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5f8b093..ca01adc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,8 +23,8 @@ class Analytics } ALIAS = { - :previousId => 1234, - :userId => 'abcd' + :previous_id => 1234, + :user_id => 'abcd' } GROUP = {} From 6fb177db502ec1dbf875db38796c20d5fb1e3165 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 13 May 2014 03:00:59 -0500 Subject: [PATCH 054/322] Generate v4 uuid (#49) --- lib/segment/analytics/utils.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 9ec6f48..a3f7ab5 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -1,3 +1,5 @@ +require 'securerandom' + module Segment class Analytics module Utils @@ -40,7 +42,10 @@ def isoify_dates!(hash) # public: Returns a uid string # def uid - (0..16).to_a.map{|x| rand(16).to_s(16)}.join + arr = SecureRandom.random_bytes(16).unpack("NnnnnN") + arr[2] = (arr[2] & 0x0fff) | 0x4000 + arr[3] = (arr[3] & 0x3fff) | 0x8000 + "%08x-%04x-%04x-%04x-%04x%08x" % arr end def datetime_in_iso8601 datetime From 3bfe99b12e9ec3bcf437540c12b19fb25b2a9747 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 13 May 2014 13:23:29 -0500 Subject: [PATCH 055/322] Start worker thread when needed --- lib/segment/analytics.rb | 2 +- lib/segment/analytics/client.rb | 40 +++++++++++-------- .../analytics/{consumer.rb => worker.rb} | 39 +++++++----------- spec/segment/analytics/client_spec.rb | 2 - .../{consumer_spec.rb => worker_spec.rb} | 30 +++++++------- spec/spec_helper.rb | 2 +- 6 files changed, 56 insertions(+), 59 deletions(-) rename lib/segment/analytics/{consumer.rb => worker.rb} (63%) rename spec/segment/analytics/{consumer_spec.rb => worker_spec.rb} (71%) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index d51b34e..83aff75 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -2,7 +2,7 @@ require 'segment/analytics/utils' require 'segment/analytics/version' require 'segment/analytics/client' -require 'segment/analytics/consumer' +require 'segment/analytics/worker' require 'segment/analytics/request' require 'segment/analytics/response' require 'segment/analytics/logging' diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 3309c15..61e3c8e 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -1,7 +1,7 @@ require 'thread' require 'time' require 'segment/analytics/utils' -require 'segment/analytics/consumer' +require 'segment/analytics/worker' require 'segment/analytics/defaults' module Segment @@ -21,27 +21,19 @@ def initialize options = {} @queue = Queue.new @write_key = options[:write_key] @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE - @consumer = Consumer.new @queue, @write_key, options - @thread = ConsumerThread.new { @consumer.run } + @options = options + @worker_mutex = Mutex.new check_write_key! - - at_exit do - # Let the consumer thread know it should exit. - @thread[:should_exit] = true - - # Push a flag value to the consumer queue in case it's blocked waiting for a value. This will allow it - # to continue its normal chain of processing, giving it a chance to exit. - @queue << nil - end end - # public: Synchronously waits until the consumer has flushed the queue. + # public: Synchronously waits until the worker has flushed the queue. # Use only for scripts which are not long-running, and will # specifically exit # def flush - while !@queue.empty? || @consumer.is_requesting? + while !@queue.empty? || @worker.is_requesting? + ensure_worker_running sleep(0.1) end end @@ -262,6 +254,7 @@ def queued_messages # returns Boolean of whether the item was added to the queue. def enqueue(action) # add our request id for tracing purposes + ensure_worker_running action[:messageId] = uid queue_full = @queue.length >= @max_queue_size @@ -313,8 +306,23 @@ def check_user_id! options fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id] end - # Sub-class thread so we have a named thread (useful for debugging in Thread.list). - class ConsumerThread < Thread + def ensure_worker_running + return if worker_running? + @worker_mutex.synchronize do + return if worker_running? + start_worker + end + end + + def worker_running? + @worker_thread && @worker_thread.alive? + end + + def start_worker + @worker_thread = Thread.new do + @worker = Worker.new @queue, @write_key, @options + @worker.run + end end end end diff --git a/lib/segment/analytics/consumer.rb b/lib/segment/analytics/worker.rb similarity index 63% rename from lib/segment/analytics/consumer.rb rename to lib/segment/analytics/worker.rb index aa1478d..f5d5461 100644 --- a/lib/segment/analytics/consumer.rb +++ b/lib/segment/analytics/worker.rb @@ -1,3 +1,4 @@ +require 'monitor' require 'segment/analytics/defaults' require 'segment/analytics/utils' require 'segment/analytics/defaults' @@ -5,18 +6,18 @@ module Segment class Analytics - class Consumer + class Worker include Segment::Analytics::Utils include Segment::Analytics::Defaults - # public: Creates a new consumer + # public: Creates a new worker # - # The consumer continuously takes messages off the queue + # The worker continuously takes messages off the queue # and makes requests to the segment.io api # - # queue - Queue synchronized between client and consumer + # queue - Queue synchronized between client and worker # write_key - String of the project's Write key - # options - Hash of consumer options + # options - Hash of worker options # batch_size - Fixnum of how many items to send in a batch # on_error - Proc of what to do on an error # @@ -27,13 +28,13 @@ def initialize(queue, write_key, options = {}) @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || Proc.new { |status, error| } @current_batch = [] - @mutex = Mutex.new + @current_batch.extend MonitorMixin end # public: Continuously runs the loop to check for new events # def run - until Thread.current[:should_exit] + loop do flush end end @@ -41,34 +42,24 @@ def run # public: Flush some events from our queue # def flush - # Block until we have something to send - item = @queue.pop - return if item.nil? + return if @queue.empty? - # Synchronize on additions to the current batch - @mutex.synchronize { - @current_batch << item + @current_batch.synchronize do until @current_batch.length >= @batch_size || @queue.empty? @current_batch << @queue.pop end - } + end - req = Request.new - res = req.post @write_key, @current_batch + res = Request.new.post @write_key, @current_batch @on_error.call res.status, res.error unless res.status == 200 - @mutex.synchronize { - @current_batch = [] - } + + @current_batch.synchronize { @current_batch.clear } end # public: Check whether we have outstanding requests. # def is_requesting? - requesting = nil - @mutex.synchronize { - requesting = !@current_batch.empty? - } - requesting + @current_batch.synchronize { !@current_batch.empty? } end end end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index aa96def..17edd40 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -20,7 +20,6 @@ class Analytics describe '#track' do before(:all) do @client = Client.new :write_key => WRITE_KEY - @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end @@ -72,7 +71,6 @@ class Analytics before(:all) do @client = Client.new :write_key => WRITE_KEY - @client.instance_variable_get(:@thread).kill @queue = @client.instance_variable_get :@queue end diff --git a/spec/segment/analytics/consumer_spec.rb b/spec/segment/analytics/worker_spec.rb similarity index 71% rename from spec/segment/analytics/consumer_spec.rb rename to spec/segment/analytics/worker_spec.rb index f4cbf0a..252144c 100644 --- a/spec/segment/analytics/consumer_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -2,12 +2,12 @@ module Segment class Analytics - describe Consumer do + describe Worker do describe "#init" do it 'accepts string keys' do queue = Queue.new - consumer = Segment::Analytics::Consumer.new(queue, 'secret', 'batch_size' => 100) - consumer.instance_variable_get(:@batch_size).should == 100 + worker = Segment::Analytics::Worker.new(queue, 'secret', 'batch_size' => 100) + worker.instance_variable_get(:@batch_size).should == 100 end end @@ -25,8 +25,8 @@ class Analytics queue = Queue.new queue << {} - consumer = Segment::Analytics::Consumer.new(queue, 'secret') - consumer.flush + worker = Segment::Analytics::Worker.new(queue, 'secret') + worker.flush queue.should be_empty @@ -44,8 +44,8 @@ class Analytics queue = Queue.new queue << {} - consumer = Segment::Analytics::Consumer.new queue, 'secret', :on_error => on_error - consumer.flush + worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error + worker.flush Segment::Analytics::Request::any_instance.unstub(:post) @@ -62,8 +62,8 @@ class Analytics queue = Queue.new queue << Requested::TRACK - consumer = Segment::Analytics::Consumer.new queue, 'testsecret', :on_error => on_error - consumer.flush + worker = Segment::Analytics::Worker.new queue, 'testsecret', :on_error => on_error + worker.flush queue.should be_empty end @@ -74,25 +74,25 @@ class Analytics it 'should not return true if there isn\'t a current batch' do queue = Queue.new - consumer = Segment::Analytics::Consumer.new(queue, 'testsecret') + worker = Segment::Analytics::Worker.new(queue, 'testsecret') - consumer.is_requesting?.should == false + worker.is_requesting?.should == false end it 'should return true if there is a current batch' do queue = Queue.new queue << Requested::TRACK - consumer = Segment::Analytics::Consumer.new(queue, 'testsecret') + worker = Segment::Analytics::Worker.new(queue, 'testsecret') Thread.new { - consumer.flush - consumer.is_requesting?.should == false + worker.flush + worker.is_requesting?.should == false } # sleep barely long enough to let thread flush the queue. sleep(0.001) - consumer.is_requesting?.should == true + worker.is_requesting?.should == true end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ca01adc..ddf93ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -49,7 +49,7 @@ module Queued SCREEN = SCREEN.merge :user_id => USER_ID end - # Hashes which are sent from the consumer, camel_cased + # Hashes which are sent from the worker, camel_cased module Requested TRACK = TRACK.merge({ :userId => USER_ID, From 8043885fc5cca2707ba1dfef4fb8c4b67b2bcc67 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 13 May 2014 22:27:00 -0500 Subject: [PATCH 056/322] Add test for flushing queue in forked process --- lib/segment/analytics/client.rb | 21 ++++++++------------- lib/segment/analytics/worker.rb | 24 +++++++++--------------- spec/segment/analytics/client_spec.rb | 13 +++++++++++++ spec/segment/analytics/worker_spec.rb | 8 ++++---- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 61e3c8e..f0317e3 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -254,12 +254,11 @@ def queued_messages # returns Boolean of whether the item was added to the queue. def enqueue(action) # add our request id for tracing purposes - ensure_worker_running action[:messageId] = uid - - queue_full = @queue.length >= @max_queue_size - @queue << action unless queue_full - + unless queue_full = @queue.length >= @max_queue_size + ensure_worker_running + @queue << action + end !queue_full end @@ -310,20 +309,16 @@ def ensure_worker_running return if worker_running? @worker_mutex.synchronize do return if worker_running? - start_worker + @worker_thread = Thread.new do + @worker = Worker.new @queue, @write_key, @options + @worker.run + end end end def worker_running? @worker_thread && @worker_thread.alive? end - - def start_worker - @worker_thread = Thread.new do - @worker = Worker.new @queue, @write_key, @options - @worker.run - end - end end end end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f5d5461..4812a25 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -35,25 +35,19 @@ def initialize(queue, write_key, options = {}) # def run loop do - flush - end - end - - # public: Flush some events from our queue - # - def flush - return if @queue.empty? + return if @queue.empty? - @current_batch.synchronize do - until @current_batch.length >= @batch_size || @queue.empty? - @current_batch << @queue.pop + @current_batch.synchronize do + until @current_batch.length >= @batch_size || @queue.empty? + @current_batch << @queue.pop + end end - end - res = Request.new.post @write_key, @current_batch - @on_error.call res.status, res.error unless res.status == 200 + res = Request.new.post @write_key, @current_batch + @on_error.call res.status, res.error unless res.status == 200 - @current_batch.synchronize { @current_batch.clear } + @current_batch.synchronize { @current_batch.clear } + end end # public: Check whether we have outstanding requests. diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 17edd40..121bbf4 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -201,6 +201,19 @@ class Analytics @client.flush @client.queued_messages.should == 0 end + + it 'should complete when the process forks' do + @client.identify Queued::IDENTIFY + + Process.fork do + @client.track Queued::TRACK + @client.flush + end + + Process.wait + + @client.queued_messages.should == 0 + end end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 252144c..014af42 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -26,7 +26,7 @@ class Analytics queue = Queue.new queue << {} worker = Segment::Analytics::Worker.new(queue, 'secret') - worker.flush + worker.run queue.should be_empty @@ -45,7 +45,7 @@ class Analytics queue = Queue.new queue << {} worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error - worker.flush + worker.run Segment::Analytics::Request::any_instance.unstub(:post) @@ -63,7 +63,7 @@ class Analytics queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new queue, 'testsecret', :on_error => on_error - worker.flush + worker.run queue.should be_empty end @@ -86,7 +86,7 @@ class Analytics worker = Segment::Analytics::Worker.new(queue, 'testsecret') Thread.new { - worker.flush + worker.run worker.is_requesting?.should == false } From fd15caf95ae428ffae5b738cc31c5e50ee9dcf5b Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 13 May 2014 22:34:19 -0500 Subject: [PATCH 057/322] Dont run fork test in jruby --- lib/segment/analytics/worker.rb | 17 ++++++++--------- spec/segment/analytics/client_spec.rb | 6 ++---- spec/segment/analytics/worker_spec.rb | 1 - 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 4812a25..7154d19 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,4 +1,3 @@ -require 'monitor' require 'segment/analytics/defaults' require 'segment/analytics/utils' require 'segment/analytics/defaults' @@ -27,8 +26,8 @@ def initialize(queue, write_key, options = {}) @write_key = write_key @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || Proc.new { |status, error| } - @current_batch = [] - @current_batch.extend MonitorMixin + @batch = [] + @lock = Mutex.new end # public: Continuously runs the loop to check for new events @@ -37,23 +36,23 @@ def run loop do return if @queue.empty? - @current_batch.synchronize do - until @current_batch.length >= @batch_size || @queue.empty? - @current_batch << @queue.pop + @lock.synchronize do + until @batch.length >= @batch_size || @queue.empty? + @batch << @queue.pop end end - res = Request.new.post @write_key, @current_batch + res = Request.new.post @write_key, @batch @on_error.call res.status, res.error unless res.status == 200 - @current_batch.synchronize { @current_batch.clear } + @lock.synchronize { @batch.clear } end end # public: Check whether we have outstanding requests. # def is_requesting? - @current_batch.synchronize { !@current_batch.empty? } + @lock.synchronize { !@batch.empty? } end end end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 121bbf4..38c2460 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -68,7 +68,6 @@ class Analytics describe '#identify' do - before(:all) do @client = Client.new :write_key => WRITE_KEY @queue = @client.instance_variable_get :@queue @@ -208,12 +207,11 @@ class Analytics Process.fork do @client.track Queued::TRACK @client.flush + @client.queued_messages.should == 0 end Process.wait - - @client.queued_messages.should == 0 - end + end unless defined? JRUBY_VERSION end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 014af42..52fb2cf 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -70,7 +70,6 @@ class Analytics end describe '#is_requesting?' do - it 'should not return true if there isn\'t a current batch' do queue = Queue.new From 39eec4f844e1b937bf57cb13113963bff19bc08e Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 14 May 2014 13:36:20 -0500 Subject: [PATCH 058/322] Add fix for jruby :rage1: --- lib/segment/analytics/client.rb | 2 ++ lib/segment/analytics/worker.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index f0317e3..399d432 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -25,6 +25,8 @@ def initialize options = {} @worker_mutex = Mutex.new check_write_key! + + at_exit { @worker_thread && @worker_thread[:should_exit] = true } end # public: Synchronously waits until the worker has flushed the queue. diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 7154d19..f26bc42 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -33,7 +33,7 @@ def initialize(queue, write_key, options = {}) # public: Continuously runs the loop to check for new events # def run - loop do + until Thread.current[:should_exit] return if @queue.empty? @lock.synchronize do From b362e6e68908f2ea47ed1e8cfbef634184143dcc Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 14 May 2014 14:09:40 -0500 Subject: [PATCH 059/322] Use ruby v2.1.0 --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index cb50681..7ec1d6d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.0.0-p247 +2.1.0 From fb0c9b95119a5871ceddbf8da3b5fab414aa5ce0 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 14 May 2014 14:31:38 -0500 Subject: [PATCH 060/322] Fix transient test --- analytics-ruby.gemspec | 1 + spec/segment/analytics/worker_spec.rb | 9 +++------ spec/spec_helper.rb | 3 +++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index d5fba7e..2bcc8f4 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -16,5 +16,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" spec.add_development_dependency('rake') + spec.add_development_dependency('wrong') spec.add_development_dependency('rspec') end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 52fb2cf..11fa3f2 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -79,19 +79,16 @@ class Analytics end it 'should return true if there is a current batch' do - queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') - Thread.new { + Thread.new do worker.run worker.is_requesting?.should == false - } + end - # sleep barely long enough to let thread flush the queue. - sleep(0.001) - worker.is_requesting?.should == true + eventually { worker.is_requesting?.should be_true } end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ddf93ad..1549a0e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,7 @@ require 'segment/analytics' +require 'wrong' + +include Wrong module Segment class Analytics From ac42450a6f7c3c2180fb4a4d660e650dd224ccd5 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 14 May 2014 16:38:36 -0500 Subject: [PATCH 061/322] Dont coerce ids to strings (fix #48) --- lib/segment/analytics/client.rb | 38 ++++++++++++++------------- spec/segment/analytics/client_spec.rb | 12 +++++++++ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 399d432..bb26f21 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -70,8 +70,8 @@ def track options enqueue({ :event => event, - :userId => options[:user_id].to_s, - :anonymousId => options[:anonymous_id].to_s, + :userId => options[:user_id], + :anonymousId => options[:anonymous_id], :context => context, :properties => properties, :timestamp => datetime_in_iso8601(timestamp), @@ -102,8 +102,8 @@ def identify options add_context context enqueue({ - :userId => options[:user_id].to_s, - :anonymousId => options[:anonymous_id].to_s, + :userId => options[:user_id], + :anonymousId => options[:anonymous_id], :context => context, :traits => traits, :timestamp => datetime_in_iso8601(timestamp), @@ -121,13 +121,13 @@ def identify options def alias(options) symbolize_keys! options - from = options[:previous_id].to_s - to = options[:user_id].to_s + from = options[:previous_id] + to = options[:user_id] timestamp = options[:timestamp] || Time.new context = options[:context] || {} - check_non_empty_string! from, 'previous_id' - check_non_empty_string! to, 'user_id' + check_presence! from, 'previous_id' + check_presence! to, 'user_id' check_timestamp! timestamp add_context context @@ -151,15 +151,15 @@ def group(options) symbolize_keys! options check_user_id! options - group_id = options[:group_id].to_s - user_id = options[:user_id].to_s + group_id = options[:group_id] + user_id = options[:user_id] traits = options[:traits] || {} timestamp = options[:timestamp] || Time.new context = options[:context] || {} fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash - check_non_empty_string! group_id, 'group_id' + check_presence! group_id, 'group_id' check_timestamp! timestamp add_context context @@ -198,8 +198,8 @@ def page(options) add_context context enqueue({ - :userId => options[:user_id].to_s, - :anonymousId => options[:anonymous_id].to_s, + :userId => options[:user_id], + :anonymousId => options[:anonymous_id], :name => name, :properties => properties, :context => context, @@ -232,8 +232,8 @@ def screen(options) add_context context enqueue({ - :userId => options[:user_id].to_s, - :anonymousId => options[:anonymous_id].to_s, + :userId => options[:user_id], + :anonymousId => options[:anonymous_id], :name => name, :properties => properties, :context => context, @@ -266,11 +266,13 @@ def enqueue(action) # private: Ensures that a string is non-empty # - # str - String that must be non-empty + # obj - String|Number that must be non-blank # name - Name of the validated value # - def check_non_empty_string!(str, name) - fail ArgumentError, "Must supply a non-empty #{name}" if str.empty? + def check_presence!(obj, name) + if obj.nil? || (obj.is_a?(String) && obj.empty?) + fail ArgumentError, "#{name} must be given" + end end # private: Adds contextual information to the call diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 38c2460..4f10dad 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -51,6 +51,18 @@ class Analytics @queue.pop end + it 'should not convert ids given as fixnums to strings' do + check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } + [:track, :screen, :page, :group, :identify, :alias].each do |s| + @client.send s, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + message = @queue.pop + check_property.call(message, :userId, 1) + check_property.call(message, :groupId, 2) + check_property.call(message, :previousId, 3) + check_property.call(message, :anonymousId, 4) + end + end + it 'should convert Time properties into iso8601 format' do @client.track({ :user_id => 'user', From 64cda6d85fc417f72da604994634d75f4784333e Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 14 May 2014 16:44:54 -0500 Subject: [PATCH 062/322] Remove blocking call in spec --- spec/segment/analytics/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 4f10dad..b905809 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -55,7 +55,7 @@ class Analytics check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } [:track, :screen, :page, :group, :identify, :alias].each do |s| @client.send s, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" - message = @queue.pop + message = @queue.pop(true) check_property.call(message, :userId, 1) check_property.call(message, :groupId, 2) check_property.call(message, :previousId, 3) From 30e53e85d147ebc2afb78a53d99db7cf13b09d46 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 15 May 2014 12:47:07 -0500 Subject: [PATCH 063/322] Add integrations option --- lib/segment/analytics/client.rb | 6 ++++ spec/segment/analytics/client_spec.rb | 42 +++++++++++++++++++-------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index bb26f21..e72605f 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -73,6 +73,7 @@ def track options :userId => options[:user_id], :anonymousId => options[:anonymous_id], :context => context, + :integrations => options[:integrations], :properties => properties, :timestamp => datetime_in_iso8601(timestamp), :type => 'track' @@ -104,6 +105,7 @@ def identify options enqueue({ :userId => options[:user_id], :anonymousId => options[:anonymous_id], + :integrations => options[:integrations], :context => context, :traits => traits, :timestamp => datetime_in_iso8601(timestamp), @@ -134,6 +136,7 @@ def alias(options) enqueue({ :previousId => from, :userId => to, + :integrations => options[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'alias' @@ -167,6 +170,7 @@ def group(options) :groupId => group_id, :userId => user_id, :traits => traits, + :integrations => options[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'group' @@ -202,6 +206,7 @@ def page(options) :anonymousId => options[:anonymous_id], :name => name, :properties => properties, + :integrations => options[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'page' @@ -236,6 +241,7 @@ def screen(options) :anonymousId => options[:anonymous_id], :name => name, :properties => properties, + :integrations => options[:integrations], :context => context, :timestamp => timestamp.iso8601, :type => 'screen' diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index b905809..225061b 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -51,18 +51,6 @@ class Analytics @queue.pop end - it 'should not convert ids given as fixnums to strings' do - check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } - [:track, :screen, :page, :group, :identify, :alias].each do |s| - @client.send s, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" - message = @queue.pop(true) - check_property.call(message, :userId, 1) - check_property.call(message, :groupId, 2) - check_property.call(message, :previousId, 3) - check_property.call(message, :anonymousId, 4) - end - end - it 'should convert Time properties into iso8601 format' do @client.track({ :user_id => 'user', @@ -225,6 +213,36 @@ class Analytics Process.wait end unless defined? JRUBY_VERSION end + + context 'common' do + check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } + + before(:all) do + @client = Client.new :write_key => WRITE_KEY + @queue = @client.instance_variable_get :@queue + end + + + it 'should not convert ids given as fixnums to strings' do + [:track, :screen, :page, :group, :identify, :alias].each do |s| + @client.send s, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + message = @queue.pop(true) + check_property.call(message, :userId, 1) + check_property.call(message, :groupId, 2) + check_property.call(message, :previousId, 3) + check_property.call(message, :anonymousId, 4) + end + end + + it 'should send integrations' do + [:track, :screen, :page, :group, :identify, :alias].each do |s| + @client.send s, :integrations => { All: true, Salesforce: false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + message = @queue.pop(true) + message[:integrations][:All].should be_true + message[:integrations][:Salesforce].should be_false + end + end + end end end end From c6ae253ba186148703a5abdf246ccf107927cc96 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 15 May 2014 12:21:17 -0500 Subject: [PATCH 064/322] 2.0.1 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 8fa12d6..5b7cc14 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,6 +1,6 @@ module Segment class Analytics - VERSION = '2.0.0' + VERSION = '2.0.1' end end From 6d39c613ce6621763212c9f72929b858ac5fa363 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 15 May 2014 13:46:30 -0500 Subject: [PATCH 065/322] Add versions for dev dependencies --- analytics-ruby.gemspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 2bcc8f4..3630063 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |spec| # Ruby 1.8 requires json spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" - spec.add_development_dependency('rake') - spec.add_development_dependency('wrong') - spec.add_development_dependency('rspec') + spec.add_development_dependency 'rake', '~> 10.3' + spec.add_development_dependency 'wrong', '~> 0.0' + spec.add_development_dependency 'rspec', '~> 2.0' end From 8f333589980b31b0950ddf1316736c824ce6f0d0 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 15 May 2014 14:04:38 -0500 Subject: [PATCH 066/322] Remove new hash syntax from spec --- spec/segment/analytics/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 225061b..170196f 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -236,7 +236,7 @@ class Analytics it 'should send integrations' do [:track, :screen, :page, :group, :identify, :alias].each do |s| - @client.send s, :integrations => { All: true, Salesforce: false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + @client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" message = @queue.pop(true) message[:integrations][:All].should be_true message[:integrations][:Salesforce].should be_false From 6fe61b6883edc03d41394709d02f6ab5243d4e37 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Sat, 17 May 2014 17:53:15 -0500 Subject: [PATCH 067/322] Fix respond_to? --- lib/segment/analytics.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 83aff75..f47293e 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -23,7 +23,7 @@ def method_missing message, *args, &block end def respond_to? method_name, include_private = false - @client.respond_to?(method_missing) || super + @client.respond_to?(method_name) || super end include Logging From 2cee4ee924b21a2472c91b91c08dab359aca52f8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 20 May 2014 00:54:28 -0500 Subject: [PATCH 068/322] Update history.md --- History.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 61219cf..efede21 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,14 @@ +2.0.1 / 2014-05-15 +================== +* add: namespace under Segment::Analytics +* add: can create multiple instances with creator method (rather than + having a singleton) +* add: logging with Logger instance or Rails.logger if available +* fix: worker continues running across forked processes +* fix: removed usage of ActiveSupport methods since its not a dependency +* fix: sending data that matches segment's new api spec + +(there is no v2.0.0) 1.1.0 / 2014-04-17 ================== @@ -95,4 +106,4 @@ 0.0.3 / 2013-01-16 ================== * Rakefile and renaming courtesy of [@kiennt](https://github.com/kiennt) -* Updated tests with mocks \ No newline at end of file +* Updated tests with mocks From b4179c15c5e0c3ff24ad5fefe76fc920629bf061 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 20 May 2014 01:37:09 -0500 Subject: [PATCH 069/322] Update history.md --- History.md | 1 + 1 file changed, 1 insertion(+) diff --git a/History.md b/History.md index efede21..be64c07 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,7 @@ * add: can create multiple instances with creator method (rather than having a singleton) * add: logging with Logger instance or Rails.logger if available +* add: able to stub requests so they just log and don't hit the server * fix: worker continues running across forked processes * fix: removed usage of ActiveSupport methods since its not a dependency * fix: sending data that matches segment's new api spec From 8aeda8987b33b4a9836fc26126f8edb751c1e40f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 21 May 2014 20:29:51 -0500 Subject: [PATCH 070/322] Add ability to stub requests with STUB env var --- lib/segment/analytics/request.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index a19897d..a0acd16 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -74,6 +74,10 @@ def post(write_key, batch) class << self attr_accessor :stub + + def stub + @stub || ENV['STUB'] + end end end end From aebd74432f38bd50d4a06fdc0b72d9db13ded20f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 23 May 2014 21:19:50 -0500 Subject: [PATCH 071/322] Fix formatting iso dates --- lib/segment/analytics/utils.rb | 2 +- spec/segment/analytics/client_spec.rb | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index a3f7ab5..8ac8d74 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -75,7 +75,7 @@ def formatted_offset time, colon = true, alternate_utc_string = nil end def seconds_to_utc_offset(seconds, colon = true) - (colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), seconds.abs, (seconds.abs % 3600)] + (colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)] end UTC_OFFSET_WITH_COLON = '%s%02d:%02d' diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 170196f..e7a143f 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -41,6 +41,20 @@ class Analytics }.to raise_error(ArgumentError) end + it 'should use the timestamp given' do + time = Time.new(1990, 7, 16, 13, 30, 0, "+05:00") + + @client.track({ + event: 'testing the timestamp', + user_id: 'joe', + timestamp: time + }) + + msg = @queue.pop + + Time.parse(msg[:timestamp]).should == time + end + it 'should not error with the required options' do @client.track Queued::TRACK @queue.pop @@ -215,7 +229,7 @@ class Analytics end context 'common' do - check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } + check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } before(:all) do @client = Client.new :write_key => WRITE_KEY From 76003560f73e500d80e81fbf84674293bfb36dd8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 23 May 2014 22:32:34 -0500 Subject: [PATCH 072/322] 2.0.2 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 5b7cc14..b72a7a7 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,6 +1,6 @@ module Segment class Analytics - VERSION = '2.0.1' + VERSION = '2.0.2' end end From 78f5d90bc9ffbcd1c372e4366c1026192d6ae4c8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 29 May 2014 20:27:07 -0500 Subject: [PATCH 073/322] Update history.md for 2.0.2 --- History.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index be64c07..665dc91 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ -2.0.1 / 2014-05-15 +2.0.2 / 2014-05-30 +================== +* fix: formatting ios dates +* fix: respond_to? implementation +* add: able to stub requests by setting STUB env var + +2.0.1 / 2014-05-15 ================== * add: namespace under Segment::Analytics * add: can create multiple instances with creator method (rather than From fce6dfc1b34cddc8ab127b2bbe163f4788bfb5af Mon Sep 17 00:00:00 2001 From: Hamilton Chapman Date: Wed, 4 Jun 2014 15:24:40 +0100 Subject: [PATCH 074/322] Early initialization of worker. Fixes #51 --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index e72605f..6d90463 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -23,6 +23,7 @@ def initialize options = {} @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE @options = options @worker_mutex = Mutex.new + @worker = Worker.new @queue, @write_key, @options check_write_key! @@ -320,7 +321,6 @@ def ensure_worker_running @worker_mutex.synchronize do return if worker_running? @worker_thread = Thread.new do - @worker = Worker.new @queue, @write_key, @options @worker.run end end From 3d3050eab53d1fbb29ed741c41f558c892c2a758 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 4 Jun 2014 22:01:09 -0400 Subject: [PATCH 075/322] Fix hash syntax for 1.8.x --- spec/segment/analytics/client_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index e7a143f..0b67c9d 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -45,9 +45,9 @@ class Analytics time = Time.new(1990, 7, 16, 13, 30, 0, "+05:00") @client.track({ - event: 'testing the timestamp', - user_id: 'joe', - timestamp: time + :event => 'testing the timestamp', + :user_id => 'joe', + :timestamp => time }) msg = @queue.pop From de7e03628fab0d653658b046fb6da0677325b755 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 4 Jun 2014 22:24:52 -0400 Subject: [PATCH 076/322] Use Time#parse since Time#new can't take args in 1.8.x --- spec/segment/analytics/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 0b67c9d..8d9e0a0 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -42,7 +42,7 @@ class Analytics end it 'should use the timestamp given' do - time = Time.new(1990, 7, 16, 13, 30, 0, "+05:00") + time = Time.parse("1990-07-16 13:30:00 UTC") @client.track({ :event => 'testing the timestamp', From 67ef11669f1205fb1397761bab4d602134db2e47 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 4 Jun 2014 23:10:23 -0400 Subject: [PATCH 077/322] 2.0.3 --- History.md | 4 ++++ lib/segment/analytics/version.rb | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 665dc91..8685f82 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.0.3 / 2014-06-04 +================== +* fix: undefined method `is_requesting?' for nil:NilClass when calling flush (#51) + 2.0.2 / 2014-05-30 ================== * fix: formatting ios dates diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index b72a7a7..b277cea 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,6 +1,5 @@ module Segment class Analytics - VERSION = '2.0.2' + VERSION = '2.0.3' end end - From 20b6ca77a514f5cbf3c457610d886810816fde2f Mon Sep 17 00:00:00 2001 From: Evan Alter Date: Wed, 11 Jun 2014 00:15:27 -0500 Subject: [PATCH 078/322] isoify dates for the group method --- lib/segment/analytics/client.rb | 1 + spec/segment/analytics/client_spec.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 6d90463..cb488cf 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -162,6 +162,7 @@ def group(options) context = options[:context] || {} fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash + isoify_dates! traits check_presence! group_id, 'group_id' check_timestamp! timestamp diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 8d9e0a0..8ff5faf 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -140,6 +140,7 @@ class Analytics describe '#group' do before :all do @client = Client.new :write_key => WRITE_KEY + @queue = @client.instance_variable_get :@queue end it 'should error without group_id' do @@ -157,6 +158,20 @@ class Analytics it 'should not error with the required options as strings' do @client.group Utils.stringify_keys(Queued::GROUP) end + + it 'should convert Time traits into iso8601 format' do + @client.group({ + :user_id => 'user', + :group_id => 'group', + :traits => { + :time => Time.utc(2013), + :nottime => 'x' + } + }) + message = @queue.pop + message[:traits][:time].should == '2013-01-01T00:00:00Z' + message[:traits][:nottime].should == 'x' + end end describe '#page' do From e18438cd93dee3515465d6ddfbdff6dea6e8cd29 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 11 Jun 2014 14:47:27 -0400 Subject: [PATCH 079/322] Flush after each case (#58) --- spec/segment/analytics/client_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 8ff5faf..1493718 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -143,6 +143,10 @@ class Analytics @queue = @client.instance_variable_get :@queue end + after :each do + @client.flush + end + it 'should error without group_id' do expect { @client.group :user_id => 'foo' }.to raise_error(ArgumentError) end From 8ebb0378d55d2bcb6c3db89f13522dd9be7d6c92 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 11 Jun 2014 16:24:34 -0400 Subject: [PATCH 080/322] 2.0.4 --- History.md | 4 ++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 8685f82..77b59f4 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.0.4 / 2014-06-11 +================== +* fix: isofying trait dates in #group + 2.0.3 / 2014-06-04 ================== * fix: undefined method `is_requesting?' for nil:NilClass when calling flush (#51) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index b277cea..95b0d0d 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.3' + VERSION = '2.0.4' end end From e390d86bc2c9d5c6f73078463bce081392cf9a65 Mon Sep 17 00:00:00 2001 From: Evan Alter Date: Tue, 24 Jun 2014 18:39:19 -0500 Subject: [PATCH 081/322] Fixing datetime_in_iso8601 so it converts DateTime correctly and adding tests that check ActiveSupport::TimeZone, DateTime, and Date for all methods that use isoify_dates --- analytics-ruby.gemspec | 2 ++ lib/segment/analytics/utils.rb | 7 ++++--- spec/segment/analytics/client_spec.rb | 26 ++++++++++++++++++++++---- spec/spec_helper.rb | 1 + 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 3630063..9e9cba1 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -18,4 +18,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' + spec.add_development_dependency 'activesupport', '~> 4.0' + end diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 8ac8d74..0df8c43 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -49,10 +49,11 @@ def uid end def datetime_in_iso8601 datetime - if datetime.is_a? Date + case datetime + when Time, DateTime + time_in_iso8601 datetime + when Date date_in_iso8601 datetime - elsif datetime.is_a? Time - time_in_iso8601 datetime else datetime end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 1493718..23d4215 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -65,17 +65,23 @@ class Analytics @queue.pop end - it 'should convert Time properties into iso8601 format' do + it 'should convert time and date traits into iso8601 format' do @client.track({ :user_id => 'user', :event => 'Event', :properties => { :time => Time.utc(2013), + :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :date_time => DateTime.new(2013,1,1), + :date => Date.new(2013,1,1), :nottime => 'x' } }) message = @queue.pop message[:properties][:time].should == '2013-01-01T00:00:00Z' + message[:properties][:time_with_zone].should == '2013-01-01T00:00:00Z' + message[:properties][:date_time].should == '2013-01-01T00:00:00Z' + message[:properties][:date].should == '2013-01-01' message[:properties][:nottime].should == 'x' end end @@ -101,16 +107,22 @@ class Analytics @queue.pop end - it 'should convert Time traits into iso8601 format' do + it 'should convert time and date traits into iso8601 format' do @client.identify({ :user_id => 'user', :traits => { :time => Time.utc(2013), + :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :date_time => DateTime.new(2013,1,1), + :date => Date.new(2013,1,1), :nottime => 'x' } }) message = @queue.pop message[:traits][:time].should == '2013-01-01T00:00:00Z' + message[:traits][:time_with_zone].should == '2013-01-01T00:00:00Z' + message[:traits][:date_time].should == '2013-01-01T00:00:00Z' + message[:traits][:date].should == '2013-01-01' message[:traits][:nottime].should == 'x' end end @@ -163,17 +175,23 @@ class Analytics @client.group Utils.stringify_keys(Queued::GROUP) end - it 'should convert Time traits into iso8601 format' do - @client.group({ + it 'should convert time and date traits into iso8601 format' do + @client.identify({ :user_id => 'user', :group_id => 'group', :traits => { :time => Time.utc(2013), + :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :date_time => DateTime.new(2013,1,1), + :date => Date.new(2013,1,1), :nottime => 'x' } }) message = @queue.pop message[:traits][:time].should == '2013-01-01T00:00:00Z' + message[:traits][:time_with_zone].should == '2013-01-01T00:00:00Z' + message[:traits][:date_time].should == '2013-01-01T00:00:00Z' + message[:traits][:date].should == '2013-01-01' message[:traits][:nottime].should == 'x' end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1549a0e..530c8ba 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'segment/analytics' require 'wrong' +require 'active_support/time' include Wrong From 24b51b6e6c75f47031bfc8e9fc336c440deeb155 Mon Sep 17 00:00:00 2001 From: Evan Alter Date: Tue, 24 Jun 2014 19:07:08 -0500 Subject: [PATCH 082/322] Fixing tests --- spec/segment/analytics/client_spec.rb | 6 +++--- spec/spec_helper.rb | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 23d4215..9505d31 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -71,7 +71,7 @@ class Analytics :event => 'Event', :properties => { :time => Time.utc(2013), - :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :time_with_zone => Time.zone.parse('2013-01-01'), :date_time => DateTime.new(2013,1,1), :date => Date.new(2013,1,1), :nottime => 'x' @@ -112,7 +112,7 @@ class Analytics :user_id => 'user', :traits => { :time => Time.utc(2013), - :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :time_with_zone => Time.zone.parse('2013-01-01'), :date_time => DateTime.new(2013,1,1), :date => Date.new(2013,1,1), :nottime => 'x' @@ -181,7 +181,7 @@ class Analytics :group_id => 'group', :traits => { :time => Time.utc(2013), - :time_with_zone => ActiveSupport::TimeZone.new('UTC').parse('2013-01-01'), + :time_with_zone => Time.zone.parse('2013-01-01'), :date_time => DateTime.new(2013,1,1), :date => Date.new(2013,1,1), :nottime => 'x' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 530c8ba..5fc2ff7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,9 @@ include Wrong +# Setting timezone for ActiveSupport::TimeWithZone to UTC +Time.zone = 'UTC' + module Segment class Analytics WRITE_KEY = 'testsecret' From d59d04dea15c211c341465a61a1fcc61a0912e01 Mon Sep 17 00:00:00 2001 From: Evan Alter Date: Wed, 25 Jun 2014 10:56:24 -0500 Subject: [PATCH 083/322] Trying to fix development dependency issue with activesupport --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 9e9cba1..923088d 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -18,6 +18,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' - spec.add_development_dependency 'activesupport', '~> 4.0' + spec.add_development_dependency 'activesupport' end From 8af5728a0edafe15e116554836b84a8a33888c92 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 15:42:59 -0500 Subject: [PATCH 084/322] Add tzinfo development dependency --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 923088d..4f9e3fd 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -18,6 +18,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' + spec.add_development_dependency 'tzinfo' spec.add_development_dependency 'activesupport' - end From 72740272cc3f5f032acc828e6036a1458fc44e5c Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 15:51:35 -0500 Subject: [PATCH 085/322] Fix activesupport dev dependency version --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 4f9e3fd..ae77c43 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -19,5 +19,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' spec.add_development_dependency 'tzinfo' - spec.add_development_dependency 'activesupport' + s.add_dependency 'activesupport', '>= 3.0.0', '<4.0.0' end From e56108f6dcef43c88ff68c0e940ca82a0fb1701a Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 15:56:10 -0500 Subject: [PATCH 086/322] Fix typo --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index ae77c43..f1f730e 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -19,5 +19,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' spec.add_development_dependency 'tzinfo' - s.add_dependency 'activesupport', '>= 3.0.0', '<4.0.0' + spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0' end From fca81bd879016915f0f3b6d9090e1f52a8c5ed7f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 16:05:52 -0500 Subject: [PATCH 087/322] Remove ruby-head from travis-ci --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7e9e41..d906485 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,4 @@ rvm: - 2.0.0 - 2.1.0 - ree - - ruby-head - - jruby \ No newline at end of file + - jruby From 9546bcb11de4584f016162b62d6cf3842d9e24b8 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 16:04:50 -0500 Subject: [PATCH 088/322] v2.0.5 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 95b0d0d..38cb9f9 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.4' + VERSION = '2.0.5' end end From c6476f043d6306c4df7ebeaa81c2788428170b56 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 26 Jun 2014 16:12:54 -0500 Subject: [PATCH 089/322] Fix tzinfo version --- analytics-ruby.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index f1f730e..854557e 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -18,6 +18,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' - spec.add_development_dependency 'tzinfo' + spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0' end From b94953295a2b34448464b50fc4201b58152b417f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 11 Aug 2014 19:01:42 -0500 Subject: [PATCH 090/322] Fix category param for page, screen (close #64) --- lib/segment/analytics/client.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index cb488cf..daf209e 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -185,6 +185,7 @@ def group(options) # :user_id - String of the id to alias from # :name - String name of the page # :properties - Hash of page properties (optional) + # :category - String of the page category (optional) # :timestamp - Time of when the pageview occured (optional) # :context - Hash of context (optional) def page(options) @@ -207,6 +208,7 @@ def page(options) :userId => options[:user_id], :anonymousId => options[:anonymous_id], :name => name, + :category => options[:category], :properties => properties, :integrations => options[:integrations], :context => context, @@ -219,6 +221,7 @@ def page(options) # options - Hash # :user_id - String of the id to alias from # :name - String name of the screen + # :category - String screen category (optional) # :properties - Hash of screen properties (optional) # :timestamp - Time of when the screen occured (optional) # :context - Hash of context (optional) @@ -243,6 +246,7 @@ def screen(options) :anonymousId => options[:anonymous_id], :name => name, :properties => properties, + :category => options[:category], :integrations => options[:integrations], :context => context, :timestamp => timestamp.iso8601, @@ -333,4 +337,3 @@ def worker_running? end end end - From 39ebbcc152053b2f97606fe4cb48e0a54a930ba6 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Tue, 12 Aug 2014 01:15:15 -0400 Subject: [PATCH 091/322] 2.0.6 --- History.md | 8 ++++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 77b59f4..d60cef2 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,11 @@ +2.0.6 / 2014-08-12 +================== +* fix: category param on #page and #screen + +2.0.5 / 2014-05-26 +================== +* fix: datetime conversions + 2.0.4 / 2014-06-11 ================== * fix: isofying trait dates in #group diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 38cb9f9..0879c06 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.5' + VERSION = '2.0.6' end end From 922835a946e2ce4ab134bdf41c4cd760ba59f2fa Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 22 Aug 2014 17:34:27 -0500 Subject: [PATCH 092/322] Update ruby to 2.1.2 --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index 7ec1d6d..eca07e4 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.0 +2.1.2 From 880effd320c162787db97c5e17950bb5c4b40585 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Fri, 22 Aug 2014 19:40:24 -0500 Subject: [PATCH 093/322] Document missing params (close #65) --- lib/segment/analytics/client.rb | 68 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index daf209e..3794e41 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -44,11 +44,13 @@ def flush # public: Tracks an event # # options - Hash - # :event - String of event name. - # :user_id - String of the user id. - # :properties - Hash of event properties. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :context - Hash of context. (optional) + # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking-api/track/#user-id) + # :context - Hash of context. (optional) + # :event - String of event name. + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :properties - Hash of event properties. (optional) + # :timestamp - Time of when the event occurred. (optional) + # :user_id - String of the user id. def track options symbolize_keys! options check_user_id! options @@ -84,10 +86,12 @@ def track options # public: Identifies a user # # options - Hash - # :user_id - String of the user id - # :traits - Hash of user traits. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :context - Hash of context. (optional) + # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) + # :context - Hash of context. (optional) + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :timestamp - Time of when the event occurred. (optional) + # :traits - Hash of user traits. (optional) + # :user_id - String of the user id def identify options symbolize_keys! options check_user_id! options @@ -117,10 +121,11 @@ def identify options # public: Aliases a user from one id to another # # options - Hash - # :previous_id - String of the id to alias from - # :user_id - String of the id to alias to - # :timestamp - Time of when the alias occured (optional) - # :context - Hash of context (optional) + # :context - Hash of context (optional) + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :previous_id - String of the id to alias from + # :timestamp - Time of when the alias occured (optional) + # :user_id - String of the id to alias to def alias(options) symbolize_keys! options @@ -147,10 +152,11 @@ def alias(options) # public: Associates a user identity with a group. # # options - Hash - # :previous_id - String of the id to alias from - # :user_id - String of the id to alias to - # :timestamp - Time of when the alias occured (optional) - # :context - Hash of context (optional) + # :context - Hash of context (optional) + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :previous_id - String of the id to alias from + # :timestamp - Time of when the alias occured (optional) + # :user_id - String of the id to alias to def group(options) symbolize_keys! options check_user_id! options @@ -182,12 +188,14 @@ def group(options) # public: Records a page view # # options - Hash - # :user_id - String of the id to alias from - # :name - String name of the page - # :properties - Hash of page properties (optional) - # :category - String of the page category (optional) - # :timestamp - Time of when the pageview occured (optional) - # :context - Hash of context (optional) + # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) + # :category - String of the page category (optional) + # :context - Hash of context (optional) + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :name - String name of the page + # :properties - Hash of page properties (optional) + # :timestamp - Time of when the pageview occured (optional) + # :user_id - String of the id to alias from def page(options) symbolize_keys! options check_user_id! options @@ -219,12 +227,14 @@ def page(options) # public: Records a screen view (for a mobile app) # # options - Hash - # :user_id - String of the id to alias from - # :name - String name of the screen - # :category - String screen category (optional) - # :properties - Hash of screen properties (optional) - # :timestamp - Time of when the screen occured (optional) - # :context - Hash of context (optional) + # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) + # :category - String screen category (optional) + # :context - Hash of context (optional) + # :integrations - Hash specifying what integrations this event goes to. (optional) + # :name - String name of the screen + # :properties - Hash of screen properties (optional) + # :timestamp - Time of when the screen occured (optional) + # :user_id - String of the id to alias from def screen(options) symbolize_keys! options check_user_id! options From 40dae2eb487810a516f33bc82d114c80cca05211 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 27 Aug 2014 21:27:45 -0500 Subject: [PATCH 094/322] s/options/attrs --- lib/segment/analytics/client.rb | 142 ++++++++++++++++---------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 3794e41..52f769c 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -11,17 +11,17 @@ class Client # public: Creates a new client # - # options - Hash + # attrs - Hash # :write_key - String of your project's write_key # :max_queue_size - Fixnum of the max calls to remain queued (optional) # :on_error - Proc which handles error calls from the API - def initialize options = {} - symbolize_keys! options + def initialize attrs = {} + symbolize_keys! attrs @queue = Queue.new - @write_key = options[:write_key] - @max_queue_size = options[:max_queue_size] || Defaults::Queue::MAX_SIZE - @options = options + @write_key = attrs[:write_key] + @max_queue_size = attrs[:max_queue_size] || Defaults::Queue::MAX_SIZE + @options = attrs @worker_mutex = Mutex.new @worker = Worker.new @queue, @write_key, @options @@ -43,7 +43,7 @@ def flush # public: Tracks an event # - # options - Hash + # attrs - Hash # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking-api/track/#user-id) # :context - Hash of context. (optional) # :event - String of event name. @@ -51,14 +51,14 @@ def flush # :properties - Hash of event properties. (optional) # :timestamp - Time of when the event occurred. (optional) # :user_id - String of the user id. - def track options - symbolize_keys! options - check_user_id! options + def track attrs + symbolize_keys! attrs + check_user_id! attrs - event = options[:event] - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + event = attrs[:event] + properties = attrs[:properties] || {} + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} check_timestamp! timestamp @@ -73,10 +73,10 @@ def track options enqueue({ :event => event, - :userId => options[:user_id], - :anonymousId => options[:anonymous_id], + :userId => attrs[:user_id], + :anonymousId => attrs[:anonymous_id], :context => context, - :integrations => options[:integrations], + :integrations => attrs[:integrations], :properties => properties, :timestamp => datetime_in_iso8601(timestamp), :type => 'track' @@ -85,20 +85,20 @@ def track options # public: Identifies a user # - # options - Hash + # attrs - Hash # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) # :context - Hash of context. (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) # :timestamp - Time of when the event occurred. (optional) # :traits - Hash of user traits. (optional) # :user_id - String of the user id - def identify options - symbolize_keys! options - check_user_id! options + def identify attrs + symbolize_keys! attrs + check_user_id! attrs - traits = options[:traits] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + traits = attrs[:traits] || {} + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} check_timestamp! timestamp @@ -108,9 +108,9 @@ def identify options add_context context enqueue({ - :userId => options[:user_id], - :anonymousId => options[:anonymous_id], - :integrations => options[:integrations], + :userId => attrs[:user_id], + :anonymousId => attrs[:anonymous_id], + :integrations => attrs[:integrations], :context => context, :traits => traits, :timestamp => datetime_in_iso8601(timestamp), @@ -120,19 +120,19 @@ def identify options # public: Aliases a user from one id to another # - # options - Hash + # attrs - Hash # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to - def alias(options) - symbolize_keys! options + def alias(attrs) + symbolize_keys! attrs - from = options[:previous_id] - to = options[:user_id] - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + from = attrs[:previous_id] + to = attrs[:user_id] + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} check_presence! from, 'previous_id' check_presence! to, 'user_id' @@ -142,7 +142,7 @@ def alias(options) enqueue({ :previousId => from, :userId => to, - :integrations => options[:integrations], + :integrations => attrs[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'alias' @@ -151,21 +151,21 @@ def alias(options) # public: Associates a user identity with a group. # - # options - Hash + # attrs - Hash # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to - def group(options) - symbolize_keys! options - check_user_id! options + def group(attrs) + symbolize_keys! attrs + check_user_id! attrs - group_id = options[:group_id] - user_id = options[:user_id] - traits = options[:traits] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + group_id = attrs[:group_id] + user_id = attrs[:user_id] + traits = attrs[:traits] || {} + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash isoify_dates! traits @@ -178,7 +178,7 @@ def group(options) :groupId => group_id, :userId => user_id, :traits => traits, - :integrations => options[:integrations], + :integrations => attrs[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'group' @@ -187,7 +187,7 @@ def group(options) # public: Records a page view # - # options - Hash + # attrs - Hash # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) # :category - String of the page category (optional) # :context - Hash of context (optional) @@ -196,14 +196,14 @@ def group(options) # :properties - Hash of page properties (optional) # :timestamp - Time of when the pageview occured (optional) # :user_id - String of the id to alias from - def page(options) - symbolize_keys! options - check_user_id! options + def page(attrs) + symbolize_keys! attrs + check_user_id! attrs - name = options[:name].to_s - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + name = attrs[:name].to_s + properties = attrs[:properties] || {} + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} fail ArgumentError, '.name must be a string' unless !name.empty? fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash @@ -213,12 +213,12 @@ def page(options) add_context context enqueue({ - :userId => options[:user_id], - :anonymousId => options[:anonymous_id], + :userId => attrs[:user_id], + :anonymousId => attrs[:anonymous_id], :name => name, - :category => options[:category], + :category => attrs[:category], :properties => properties, - :integrations => options[:integrations], + :integrations => attrs[:integrations], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'page' @@ -226,7 +226,7 @@ def page(options) end # public: Records a screen view (for a mobile app) # - # options - Hash + # attrs - Hash # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) # :category - String screen category (optional) # :context - Hash of context (optional) @@ -235,14 +235,14 @@ def page(options) # :properties - Hash of screen properties (optional) # :timestamp - Time of when the screen occured (optional) # :user_id - String of the id to alias from - def screen(options) - symbolize_keys! options - check_user_id! options + def screen(attrs) + symbolize_keys! attrs + check_user_id! attrs - name = options[:name].to_s - properties = options[:properties] || {} - timestamp = options[:timestamp] || Time.new - context = options[:context] || {} + name = attrs[:name].to_s + properties = attrs[:properties] || {} + timestamp = attrs[:timestamp] || Time.new + context = attrs[:context] || {} fail ArgumentError, '.name must be a string' if name.empty? fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash @@ -252,12 +252,12 @@ def screen(options) add_context context enqueue({ - :userId => options[:user_id], - :anonymousId => options[:anonymous_id], + :userId => attrs[:user_id], + :anonymousId => attrs[:anonymous_id], :name => name, :properties => properties, - :category => options[:category], - :integrations => options[:integrations], + :category => attrs[:category], + :integrations => attrs[:integrations], :context => context, :timestamp => timestamp.iso8601, :type => 'screen' @@ -327,8 +327,8 @@ def event attrs } end - def check_user_id! options - fail ArgumentError, 'Must supply either user_id or anonymous_id' unless options[:user_id] || options[:anonymous_id] + def check_user_id! attrs + fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] end def ensure_worker_running From f333d0f92d72206c09d9204967dce5804191cabc Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 27 Aug 2014 21:33:16 -0500 Subject: [PATCH 095/322] Add optional options hash to calls --- lib/segment/analytics/client.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 52f769c..50c63b7 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -44,10 +44,11 @@ def flush # public: Tracks an event # # attrs - Hash - # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking-api/track/#user-id) + # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) # :context - Hash of context. (optional) # :event - String of event name. # :integrations - Hash specifying what integrations this event goes to. (optional) + # :options - Hash specifying options such as user traits. (optional) # :properties - Hash of event properties. (optional) # :timestamp - Time of when the event occurred. (optional) # :user_id - String of the user id. @@ -76,6 +77,7 @@ def track attrs :userId => attrs[:user_id], :anonymousId => attrs[:anonymous_id], :context => context, + :options => attrs[:options], :integrations => attrs[:integrations], :properties => properties, :timestamp => datetime_in_iso8601(timestamp), @@ -89,6 +91,7 @@ def track attrs # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) # :context - Hash of context. (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) + # :options - Hash specifying options such as user traits. (optional) # :timestamp - Time of when the event occurred. (optional) # :traits - Hash of user traits. (optional) # :user_id - String of the user id @@ -113,6 +116,7 @@ def identify attrs :integrations => attrs[:integrations], :context => context, :traits => traits, + :options => attrs[:options], :timestamp => datetime_in_iso8601(timestamp), :type => 'identify' }) @@ -123,6 +127,7 @@ def identify attrs # attrs - Hash # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) + # :options - Hash specifying options such as user traits. (optional) # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to @@ -144,6 +149,7 @@ def alias(attrs) :userId => to, :integrations => attrs[:integrations], :context => context, + :options => attrs[:options], :timestamp => datetime_in_iso8601(timestamp), :type => 'alias' }) @@ -154,6 +160,7 @@ def alias(attrs) # attrs - Hash # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) + # :options - Hash specifying options such as user traits. (optional) # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to @@ -179,6 +186,7 @@ def group(attrs) :userId => user_id, :traits => traits, :integrations => attrs[:integrations], + :options => attrs[:options], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'group' @@ -193,6 +201,7 @@ def group(attrs) # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) # :name - String name of the page + # :options - Hash specifying options such as user traits. (optional) # :properties - Hash of page properties (optional) # :timestamp - Time of when the pageview occured (optional) # :user_id - String of the id to alias from @@ -219,6 +228,7 @@ def page(attrs) :category => attrs[:category], :properties => properties, :integrations => attrs[:integrations], + :options => attrs[:options], :context => context, :timestamp => datetime_in_iso8601(timestamp), :type => 'page' @@ -232,6 +242,7 @@ def page(attrs) # :context - Hash of context (optional) # :integrations - Hash specifying what integrations this event goes to. (optional) # :name - String name of the screen + # :options - Hash specifying options such as user traits. (optional) # :properties - Hash of screen properties (optional) # :timestamp - Time of when the screen occured (optional) # :user_id - String of the id to alias from @@ -257,6 +268,7 @@ def screen(attrs) :name => name, :properties => properties, :category => attrs[:category], + :options => attrs[:options], :integrations => attrs[:integrations], :context => context, :timestamp => timestamp.iso8601, From affd1dbff596985f22adb565035f8978c3b3d320 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Wed, 27 Aug 2014 21:33:51 -0500 Subject: [PATCH 096/322] Release 2.0.7 --- History.md | 4 ++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index d60cef2..7ad8774 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.0.7 / 2014-08-27 +================== +* fix: include optional options hash in calls + 2.0.6 / 2014-08-12 ================== * fix: category param on #page and #screen diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 0879c06..5e1d51c 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.6' + VERSION = '2.0.7' end end From e82a872d1dfe8327b85c45e41ce3083f0345412a Mon Sep 17 00:00:00 2001 From: amillet89 Date: Thu, 11 Sep 2014 22:16:47 +0000 Subject: [PATCH 097/322] Fixed timestamp rounding issue Some of our users were complaining that when sending two events in succession, they had identical timestamps. Adjusted util method to round to 3 millisecs by default and not 0. --- lib/segment/analytics/utils.rb | 6 ++++-- spec/segment/analytics/client_spec.rb | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 0df8c43..db96405 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -50,8 +50,10 @@ def uid def datetime_in_iso8601 datetime case datetime - when Time, DateTime + when Time time_in_iso8601 datetime + when DateTime + time_in_iso8601 datetime.to_time when Date date_in_iso8601 datetime else @@ -59,7 +61,7 @@ def datetime_in_iso8601 datetime end end - def time_in_iso8601 time, fraction_digits = 0 + def time_in_iso8601 time, fraction_digits = 3 fraction = if fraction_digits > 0 (".%06i" % time.usec)[0, fraction_digits + 1] end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 9505d31..09ab934 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -42,7 +42,7 @@ class Analytics end it 'should use the timestamp given' do - time = Time.parse("1990-07-16 13:30:00 UTC") + time = Time.parse("1990-07-16 13:30:00.123 UTC") @client.track({ :event => 'testing the timestamp', @@ -78,9 +78,9 @@ class Analytics } }) message = @queue.pop - message[:properties][:time].should == '2013-01-01T00:00:00Z' - message[:properties][:time_with_zone].should == '2013-01-01T00:00:00Z' - message[:properties][:date_time].should == '2013-01-01T00:00:00Z' + message[:properties][:time].should == '2013-01-01T00:00:00.000Z' + message[:properties][:time_with_zone].should == '2013-01-01T00:00:00.000Z' + message[:properties][:date_time].should == '2013-01-01T00:00:00.000Z' message[:properties][:date].should == '2013-01-01' message[:properties][:nottime].should == 'x' end @@ -119,9 +119,9 @@ class Analytics } }) message = @queue.pop - message[:traits][:time].should == '2013-01-01T00:00:00Z' - message[:traits][:time_with_zone].should == '2013-01-01T00:00:00Z' - message[:traits][:date_time].should == '2013-01-01T00:00:00Z' + message[:traits][:time].should == '2013-01-01T00:00:00.000Z' + message[:traits][:time_with_zone].should == '2013-01-01T00:00:00.000Z' + message[:traits][:date_time].should == '2013-01-01T00:00:00.000Z' message[:traits][:date].should == '2013-01-01' message[:traits][:nottime].should == 'x' end @@ -188,9 +188,9 @@ class Analytics } }) message = @queue.pop - message[:traits][:time].should == '2013-01-01T00:00:00Z' - message[:traits][:time_with_zone].should == '2013-01-01T00:00:00Z' - message[:traits][:date_time].should == '2013-01-01T00:00:00Z' + message[:traits][:time].should == '2013-01-01T00:00:00.000Z' + message[:traits][:time_with_zone].should == '2013-01-01T00:00:00.000Z' + message[:traits][:date_time].should == '2013-01-01T00:00:00.000Z' message[:traits][:date].should == '2013-01-01' message[:traits][:nottime].should == 'x' end From 401fc42e28e8fbbbad61bd62c68938945f5c9b73 Mon Sep 17 00:00:00 2001 From: amillet89 Date: Thu, 11 Sep 2014 22:33:14 +0000 Subject: [PATCH 098/322] Release 2.0.8 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 5e1d51c..be64e0a 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.7' + VERSION = '2.0.8' end end From 04d478a01b6a2794ba3fa887cad7a4d784c1d0fe Mon Sep 17 00:00:00 2001 From: amillet89 Date: Thu, 11 Sep 2014 22:35:25 +0000 Subject: [PATCH 099/322] Updated History.md --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index 7ad8774..8e07a04 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.0.8 / 2014-09-11 +================== +* fix: add 3 ms to timestamp + 2.0.7 / 2014-08-27 ================== * fix: include optional options hash in calls From 8432817b5a02aebec2f38702ac20c3cc63540081 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 23:31:50 -0400 Subject: [PATCH 100/322] Fix rescuing timeouts --- lib/segment/analytics/request.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index a0acd16..c268ac3 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -55,8 +55,7 @@ def post(write_key, batch) body = JSON.parse(res.body) error = body["error"] end - - rescue Exception => e + rescue Exception, Timeout::Error => e logger.error e.message e.backtrace.each { |line| logger.error line } status = -1 From f3d4fc1fe4f218b96568dd39b66339b36b1e9209 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 22:35:06 -0500 Subject: [PATCH 101/322] Release 2.0.9 --- History.md | 6 ++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 8e07a04..6965b47 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ + +2.0.9 / 2014-09-22 +================== + + * Fix rescuing timeouts + 2.0.8 / 2014-09-11 ================== * fix: add 3 ms to timestamp diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index be64e0a..0b88ece 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.8' + VERSION = '2.0.9' end end From a13001701817bbb3963c4de8aa2a93ed5885a56f Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 22:43:33 -0500 Subject: [PATCH 102/322] Exception will catch interrupts --- lib/segment/analytics/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index c268ac3..71c361f 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -55,7 +55,7 @@ def post(write_key, batch) body = JSON.parse(res.body) error = body["error"] end - rescue Exception, Timeout::Error => e + rescue Exception => e logger.error e.message e.backtrace.each { |line| logger.error line } status = -1 From f7cdf48db4166e6e9181db1146a704dc62abcc60 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 22:47:51 -0500 Subject: [PATCH 103/322] Move retry above error output --- lib/segment/analytics/request.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 71c361f..99df829 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -56,16 +56,15 @@ def post(write_key, batch) error = body["error"] end rescue Exception => e - logger.error e.message - e.backtrace.each { |line| logger.error line } - status = -1 - error = "Connection error: #{e}" - logger.info "retries remaining: #{remaining_retries}" - unless (remaining_retries -=1).zero? sleep(backoff) retry end + + logger.error e.message + e.backtrace.each { |line| logger.error line } + status = -1 + error = "Connection error: #{e}" end Response.new status, error From 125d9bbd3267f7f1f00eee9a73c7bb453a24ecd4 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 22:49:04 -0500 Subject: [PATCH 104/322] Update history.md --- History.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/History.md b/History.md index 6965b47..2703090 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +2.0.10 / 2014-09-22 +================== + + * Move timeout retry above output + 2.0.9 / 2014-09-22 ================== From 0ef8bd67603f68fb0bc918d1c8ee48111c966dbb Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Mon, 22 Sep 2014 22:49:42 -0500 Subject: [PATCH 105/322] Release 2.0.10 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 0b88ece..1457628 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.9' + VERSION = '2.0.10' end end From 088426f1d622a58edd3659b682f7912717a4105d Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 6 Nov 2014 23:13:12 -0500 Subject: [PATCH 106/322] Fix worker to clear batch only when its request has succesfully finished --- lib/segment/analytics/worker.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f26bc42..6dd8ee1 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -43,9 +43,12 @@ def run end res = Request.new.post @write_key, @batch - @on_error.call res.status, res.error unless res.status == 200 - @lock.synchronize { @batch.clear } + if res.status == 200 + @lock.synchronize { @batch.clear } + else + @on_error.call res.status, res.error unless res.status == 200 + end end end From 8295142d90b7514608eeb6a8f6ff0490298fa988 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 6 Nov 2014 23:15:27 -0500 Subject: [PATCH 107/322] Release 2.0.11 --- History.md | 6 +++++- lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 2703090..7e08309 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.0.11 / 2014-09-22 +================== + + * fix: don't clear batch if request failed 2.0.10 / 2014-09-22 ================== @@ -11,7 +15,7 @@ 2.0.8 / 2014-09-11 ================== -* fix: add 3 ms to timestamp +* fix: add 3 ms to timestamp 2.0.7 / 2014-08-27 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 1457628..f09ebc9 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.10' + VERSION = '2.0.11' end end From c55a0ca7c0ae8fcac697358628d4607ffa8468fa Mon Sep 17 00:00:00 2001 From: Ilya Volodarsky Date: Thu, 4 Dec 2014 11:07:59 -0800 Subject: [PATCH 108/322] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18eddf6..dd6a87c 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ analytics-ruby [![Build Status](https://travis-ci.org/segmentio/analytics-ruby.png?branch=master)](https://travis-ci.org/segmentio/analytics-ruby) -analytics-ruby is a ruby client for [Segment.io](https://segment.io) +analytics-ruby is a ruby client for [Segment](https://segment.com) ## Documentation -Documentation is available at [segment.io/libraries/ruby](https://segment.io/libraries/ruby) +Documentation is available at [segment.com/libraries/ruby](https://segment.com/libraries/ruby) ## License @@ -26,7 +26,7 @@ WWWWWW||WWWWWW (The MIT License) -Copyright (c) 2013 Segment.io Inc. +Copyright (c) 2013 Segment Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 217dc2d6895f0e6bad2bc0464e0e244af695197b Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Sat, 10 Jan 2015 02:59:55 -0500 Subject: [PATCH 109/322] Fix batch being cleared --- lib/segment/analytics/worker.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 6dd8ee1..dd244b6 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -44,11 +44,9 @@ def run res = Request.new.post @write_key, @batch - if res.status == 200 - @lock.synchronize { @batch.clear } - else - @on_error.call res.status, res.error unless res.status == 200 - end + @lock.synchronize { @batch.clear } + + @on_error.call res.status, res.error unless res.status == 200 end end From 551e234a5959b9b6f237c4c8154d7f9696c433a9 Mon Sep 17 00:00:00 2001 From: Ilya Volodarsky Date: Thu, 4 Dec 2014 11:07:59 -0800 Subject: [PATCH 110/322] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18eddf6..dd6a87c 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ analytics-ruby [![Build Status](https://travis-ci.org/segmentio/analytics-ruby.png?branch=master)](https://travis-ci.org/segmentio/analytics-ruby) -analytics-ruby is a ruby client for [Segment.io](https://segment.io) +analytics-ruby is a ruby client for [Segment](https://segment.com) ## Documentation -Documentation is available at [segment.io/libraries/ruby](https://segment.io/libraries/ruby) +Documentation is available at [segment.com/libraries/ruby](https://segment.com/libraries/ruby) ## License @@ -26,7 +26,7 @@ WWWWWW||WWWWWW (The MIT License) -Copyright (c) 2013 Segment.io Inc. +Copyright (c) 2013 Segment Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 4ed8c9f542cfac0bea9c5d7aa3d9d613125d5ded Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Sat, 10 Jan 2015 03:01:26 -0500 Subject: [PATCH 111/322] Release 2.0.12 --- History.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/History.md b/History.md index 7e08309..cbdb33f 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.0.12 / 2015-01-10 +================== + + * Fix batch being cleared and causing duplicates + 2.0.11 / 2014-09-22 ================== From 0126259e0af30fb16dc85b2412be7d0cf56dfb91 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Sat, 10 Jan 2015 03:03:42 -0500 Subject: [PATCH 112/322] Release 2.0.12 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index f09ebc9..c00828b 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.11' + VERSION = '2.0.12' end end From ebb7b4b034526b10a7838345e2b321d2201eb657 Mon Sep 17 00:00:00 2001 From: Jordan Moncharmont Date: Thu, 19 Feb 2015 19:17:04 +0100 Subject: [PATCH 113/322] Add testing notes to README.md Tell people about the `stub` option, it makes testing with this library a lot simpler. Probably should go on your website too, but I don't think I can edit that. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index dd6a87c..ba1c73e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ analytics-ruby is a ruby client for [Segment](https://segment.com) Documentation is available at [segment.com/libraries/ruby](https://segment.com/libraries/ruby) +## Testing + +You can use the `stub` option to Segment::Analytics.new to cause all requests to be stubbed, making it easier to test with this library. + ## License ``` From fd83bced40e29583dce032efc2c3bd1ff3bc3521 Mon Sep 17 00:00:00 2001 From: Ajay Dhesikan Date: Fri, 21 Aug 2015 19:58:12 -0700 Subject: [PATCH 114/322] Add install and usage instructions to README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index dd6a87c..1140594 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,54 @@ analytics-ruby analytics-ruby is a ruby client for [Segment](https://segment.com) +## Install + +Into Gemfile from rubygems.org: +``` +gem 'analytics-ruby', :require => "segment" +``` + +Into environment gems from rubygems.org: +``` +gem install 'analytics-ruby' +``` + +## Usage + +Create an instance of the Analytics object: +``` +analytics = Segment::Analytics.new({ + write_key: 'YOUR_WRITE_KEY' +}) +``` + +Sample usage: +``` +user = User.last + +# Identify the user for the people section +analytics.identify( + { + user_id: user.id, + traits: { + email: user.email, + first_name: user.first_name, + last_name: user.last_name + } + } +) + +# Track a user event +analytics.track( + { + user_id: user.id, + event: 'Created Account' + } +) +``` + +Refer to the section below for documenation on individual available calls. + ## Documentation Documentation is available at [segment.com/libraries/ruby](https://segment.com/libraries/ruby) From ca5efa1e2f1fd4f2a6404c0081c824fb3f82c62f Mon Sep 17 00:00:00 2001 From: Paul Dlug Date: Tue, 8 Sep 2015 11:54:49 -0400 Subject: [PATCH 115/322] Do not require a name when tracking page requests, fixes #80 --- lib/segment/analytics/client.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 50c63b7..5c90792 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -214,7 +214,6 @@ def page(attrs) timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} - fail ArgumentError, '.name must be a string' unless !name.empty? fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties From 962c0ffc8dc4a2947348de4508c30e0d5c4dd0df Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:21:26 -0500 Subject: [PATCH 116/322] page: fix test to allow no name --- spec/segment/analytics/client_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 09ab934..f6d2a25 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -205,10 +205,6 @@ class Analytics expect { @client.page :name => 'foo' }.to raise_error(ArgumentError) end - it 'should error without name' do - expect { @client.page :user_id => 1 }.to raise_error(ArgumentError) - end - it 'should not error with the required options' do @client.page Queued::PAGE end From 8ca21014c4a611539198ba475d272bc3fd406fda Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:25:23 -0500 Subject: [PATCH 117/322] git: ignore ruby version --- .gitignore | 1 + .ruby-version | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .ruby-version diff --git a/.gitignore b/.gitignore index cec3cb5..83f5868 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.gem Gemfile.lock +.ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index eca07e4..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.1.2 From ce95502c3566061af32510b1e3c0ece8655d9e88 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:25:39 -0500 Subject: [PATCH 118/322] page/screen: allow no name --- lib/segment/analytics/client.rb | 1 - spec/segment/analytics/client_spec.rb | 4 ---- spec/segment/analytics_spec.rb | 9 --------- 3 files changed, 14 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 5c90792..b28f29d 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -254,7 +254,6 @@ def screen(attrs) timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} - fail ArgumentError, '.name must be a string' if name.empty? fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index f6d2a25..63e6653 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -223,10 +223,6 @@ class Analytics expect { @client.screen :name => 'foo' }.to raise_error(ArgumentError) end - it 'should error without name' do - expect { A@client.screen :user_id => 1 }.to raise_error(ArgumentError) - end - it 'should not error with the required options' do @client.screen Queued::SCREEN end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index ef47968..b04192f 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -67,10 +67,6 @@ class Analytics expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError) end - it 'should error without name' do - expect { analytics.page :user_id => 1 }.to raise_error(ArgumentError) - end - it 'should not error with the required options' do analytics.page Queued::PAGE sleep(1) @@ -85,11 +81,6 @@ class Analytics it 'should error without name' do expect { analytics.screen :user_id => 1 }.to raise_error(ArgumentError) end - - it 'should not error with the required options' do - analytics.screen Queued::SCREEN - sleep(1) - end end describe '#flush' do From 88cca8cc41f744677f65caa9676a40b8eb3135e9 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:27:02 -0500 Subject: [PATCH 119/322] screen: remove test to check for name --- spec/segment/analytics_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index b04192f..fe276c2 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -77,10 +77,6 @@ class Analytics it 'should error without user_id or anonymous_id' do expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) end - - it 'should error without name' do - expect { analytics.screen :user_id => 1 }.to raise_error(ArgumentError) - end end describe '#flush' do From 52f902304b1a156ceb349105782ab46ec911444a Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:47:57 -0500 Subject: [PATCH 120/322] travis: remvoe old rubys --- .travis.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d906485..1a02c9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,7 @@ language: ruby rvm: - - jruby-18mode - jruby-19mode - - 1.8.7 - - 1.9.2 - 1.9.3 - 2.0.0 - 2.1.0 - - ree - - jruby From 21b0e4fdf84dd2a1da2025385ec320900709c4f4 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 03:56:11 -0500 Subject: [PATCH 121/322] screen: readd test --- spec/segment/analytics_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index fe276c2..3265a46 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -77,6 +77,11 @@ class Analytics it 'should error without user_id or anonymous_id' do expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) end + + it 'should not error with the required options' do + analytics.screen Queued::SCREEN + sleep(1) + end end describe '#flush' do From b5c196cbbaed454b0c13bdd2f8b47cb69cce12b2 Mon Sep 17 00:00:00 2001 From: Travis Jeffery Date: Thu, 10 Sep 2015 04:02:21 -0500 Subject: [PATCH 122/322] Release 2.0.13 --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index c00828b..f711ab0 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.12' + VERSION = '2.0.13' end end From eb8e76747304ac53dc36723d5fa5765ec60f270f Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Thu, 22 Oct 2015 22:49:50 -0700 Subject: [PATCH 123/322] Update client spec conventions and assertions Instead of relying on an error being raised with an empty spec, assert that one is not raised explicitly. Use expect instead of should assertions Break out grouped spec, they were relying on some values being nil and a should block not being called due to short circuiting. --- spec/segment/analytics/client_spec.rb | 289 +++++++++++++------------- 1 file changed, 150 insertions(+), 139 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 63e6653..7c45d07 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -3,37 +3,39 @@ module Segment class Analytics describe Client do + let(:client) { Client.new :write_key => WRITE_KEY } + let(:queue) { client.instance_variable_get :@queue } + describe '#initialize' do - it 'should error if no write_key is supplied' do + it 'errors if no write_key is supplied' do expect { Client.new }.to raise_error(ArgumentError) end - it 'should not error if a write_key is supplied' do - Client.new :write_key => WRITE_KEY + it 'does not error if a write_key is supplied' do + expect do + Client.new :write_key => WRITE_KEY + end.to_not raise_error end - it 'should not error if a write_key is supplied as a string' do - Client.new 'write_key' => WRITE_KEY + it 'does not error if a write_key is supplied as a string' do + expect do + Client.new 'write_key' => WRITE_KEY + end.to_not raise_error end end describe '#track' do - before(:all) do - @client = Client.new :write_key => WRITE_KEY - @queue = @client.instance_variable_get :@queue - end - - it 'should error without an event' do - expect { @client.track(:user_id => 'user') }.to raise_error(ArgumentError) + it 'errors without an event' do + expect { client.track(:user_id => 'user') }.to raise_error(ArgumentError) end - it 'should error without a user_id' do - expect { @client.track(:event => 'Event') }.to raise_error(ArgumentError) + it 'errors without a user_id' do + expect { client.track(:event => 'Event') }.to raise_error(ArgumentError) end - it 'should error if properties is not a hash' do + it 'errors if properties is not a hash' do expect { - @client.track({ + client.track({ :user_id => 'user', :event => 'Event', :properties => [1,2,3] @@ -41,32 +43,36 @@ class Analytics }.to raise_error(ArgumentError) end - it 'should use the timestamp given' do + it 'uses the timestamp given' do time = Time.parse("1990-07-16 13:30:00.123 UTC") - @client.track({ + client.track({ :event => 'testing the timestamp', :user_id => 'joe', :timestamp => time }) - msg = @queue.pop + msg = queue.pop - Time.parse(msg[:timestamp]).should == time + expect(Time.parse(msg[:timestamp])).to eq(time) end - it 'should not error with the required options' do - @client.track Queued::TRACK - @queue.pop + it 'does not error with the required options' do + expect do + client.track Queued::TRACK + queue.pop + end.to_not raise_error end - it 'should not error when given string keys' do - @client.track Utils.stringify_keys(Queued::TRACK) - @queue.pop + it 'does not error when given string keys' do + expect do + client.track Utils.stringify_keys(Queued::TRACK) + queue.pop + end.to_not raise_error end - it 'should convert time and date traits into iso8601 format' do - @client.track({ + it 'converts time and date traits into iso8601 format' do + client.track({ :user_id => 'user', :event => 'Event', :properties => { @@ -77,38 +83,38 @@ class Analytics :nottime => 'x' } }) - message = @queue.pop - message[:properties][:time].should == '2013-01-01T00:00:00.000Z' - message[:properties][:time_with_zone].should == '2013-01-01T00:00:00.000Z' - message[:properties][:date_time].should == '2013-01-01T00:00:00.000Z' - message[:properties][:date].should == '2013-01-01' - message[:properties][:nottime].should == 'x' + message = queue.pop + + expect(message[:properties][:time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:properties][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:properties][:date_time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:properties][:date]).to eq('2013-01-01') + expect(message[:properties][:nottime]).to eq('x') end end describe '#identify' do - before(:all) do - @client = Client.new :write_key => WRITE_KEY - @queue = @client.instance_variable_get :@queue - end - - it 'should error without any user id' do - expect { @client.identify({}) }.to raise_error(ArgumentError) + it 'errors without any user id' do + expect { client.identify({}) }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - @client.identify Queued::IDENTIFY - @queue.pop + it 'does not error with the required options' do + expect do + client.identify Queued::IDENTIFY + queue.pop + end.to_not raise_error end - it 'should not error with the required options as strings' do - @client.identify Utils.stringify_keys(Queued::IDENTIFY) - @queue.pop + it 'does not error with the required options as strings' do + expect do + client.identify Utils.stringify_keys(Queued::IDENTIFY) + queue.pop + end.to_not raise_error end it 'should convert time and date traits into iso8601 format' do - @client.identify({ + client.identify({ :user_id => 'user', :traits => { :time => Time.utc(2013), @@ -118,65 +124,60 @@ class Analytics :nottime => 'x' } }) - message = @queue.pop - message[:traits][:time].should == '2013-01-01T00:00:00.000Z' - message[:traits][:time_with_zone].should == '2013-01-01T00:00:00.000Z' - message[:traits][:date_time].should == '2013-01-01T00:00:00.000Z' - message[:traits][:date].should == '2013-01-01' - message[:traits][:nottime].should == 'x' + + message = queue.pop + + expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:date]).to eq('2013-01-01') + expect(message[:traits][:nottime]).to eq('x') end end describe '#alias' do - before :all do - @client = Client.new :write_key => WRITE_KEY - end - - it 'should error without from' do - expect { @client.alias :user_id => 1234 }.to raise_error(ArgumentError) + it 'errors without from' do + expect { client.alias :user_id => 1234 }.to raise_error(ArgumentError) end - it 'should error without to' do - expect { @client.alias :previous_id => 1234 }.to raise_error(ArgumentError) + it 'errors without to' do + expect { client.alias :previous_id => 1234 }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - @client.alias ALIAS + it 'does not error with the required options' do + expect { client.alias ALIAS }.to_not raise_error end - it 'should not error with the required options as strings' do - @client.alias Utils.stringify_keys(ALIAS) + it 'does not error with the required options as strings' do + expect do + client.alias Utils.stringify_keys(ALIAS) + end.to_not raise_error end end describe '#group' do - before :all do - @client = Client.new :write_key => WRITE_KEY - @queue = @client.instance_variable_get :@queue + after do + client.flush end - after :each do - @client.flush + it 'errors without group_id' do + expect { client.group :user_id => 'foo' }.to raise_error(ArgumentError) end - it 'should error without group_id' do - expect { @client.group :user_id => 'foo' }.to raise_error(ArgumentError) + it 'errors without user_id' do + expect { client.group :group_id => 'foo' }.to raise_error(ArgumentError) end - it 'should error without user_id' do - expect { @client.group :group_id => 'foo' }.to raise_error(ArgumentError) + it 'does not error with the required options' do + client.group Queued::GROUP end - it 'should not error with the required options' do - @client.group Queued::GROUP + it 'does not error with the required options as strings' do + client.group Utils.stringify_keys(Queued::GROUP) end - it 'should not error with the required options as strings' do - @client.group Utils.stringify_keys(Queued::GROUP) - end - - it 'should convert time and date traits into iso8601 format' do - @client.identify({ + it 'converts time and date traits into iso8601 format' do + client.identify({ :user_id => 'user', :group_id => 'group', :traits => { @@ -187,70 +188,65 @@ class Analytics :nottime => 'x' } }) - message = @queue.pop - message[:traits][:time].should == '2013-01-01T00:00:00.000Z' - message[:traits][:time_with_zone].should == '2013-01-01T00:00:00.000Z' - message[:traits][:date_time].should == '2013-01-01T00:00:00.000Z' - message[:traits][:date].should == '2013-01-01' - message[:traits][:nottime].should == 'x' + + message = queue.pop + + expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z') + expect(message[:traits][:date]).to eq('2013-01-01') + expect(message[:traits][:nottime]).to eq('x') end end describe '#page' do - before :all do - @client = Client.new :write_key => WRITE_KEY - end - - it 'should error without user_id' do - expect { @client.page :name => 'foo' }.to raise_error(ArgumentError) + it 'errors without user_id' do + expect { client.page :name => 'foo' }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - @client.page Queued::PAGE + it 'does not error with the required options' do + expect { client.page Queued::PAGE }.to_not raise_error end - it 'should not error with the required options as strings' do - @client.page Utils.stringify_keys(Queued::PAGE) + it 'does not error with the required options as strings' do + expect do + client.page Utils.stringify_keys(Queued::PAGE) + end.to_not raise_error end end describe '#screen' do - before :all do - @client = Client.new :write_key => WRITE_KEY + it 'errors without user_id' do + expect { client.screen :name => 'foo' }.to raise_error(ArgumentError) end - it 'should error without user_id' do - expect { @client.screen :name => 'foo' }.to raise_error(ArgumentError) + it 'does not error with the required options' do + expect { client.screen Queued::SCREEN }.to_not raise_error end - it 'should not error with the required options' do - @client.screen Queued::SCREEN - end - - it 'should not error with the required options as strings' do - @client.screen Utils.stringify_keys(Queued::SCREEN) + it 'does not error with the required options as strings' do + expect do + client.screen Utils.stringify_keys(Queued::SCREEN) + end.to_not raise_error end end describe '#flush' do - before(:all) do - @client = Client.new :write_key => WRITE_KEY - end + it 'waits for the queue to finish on a flush' do + client.identify Queued::IDENTIFY + client.track Queued::TRACK + client.flush - it 'should wait for the queue to finish on a flush' do - @client.identify Queued::IDENTIFY - @client.track Queued::TRACK - @client.flush - @client.queued_messages.should == 0 + expect(client.queued_messages).to eq(0) end - it 'should complete when the process forks' do - @client.identify Queued::IDENTIFY + it 'completes when the process forks' do + client.identify Queued::IDENTIFY Process.fork do - @client.track Queued::TRACK - @client.flush - @client.queued_messages.should == 0 + client.track Queued::TRACK + client.flush + expect(client.queued_messages).to eq(0) end Process.wait @@ -258,31 +254,46 @@ class Analytics end context 'common' do - check_property = proc { |msg, k, v| msg[k] && msg[k].should == v } + check_property = proc { |msg, k, v| msg[k] && msg[k] == v } + + let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" } } - before(:all) do - @client = Client.new :write_key => WRITE_KEY - @queue = @client.instance_variable_get :@queue + it 'does not convert ids given as fixnums to strings' do + [:track, :screen, :page, :identify].each do |s| + client.send(s, data) + message = queue.pop(true) + + expect(check_property.call(message, :userId, 1)).to eq(true) + expect(check_property.call(message, :anonymousId, 4)).to eq(true) + end end + context 'group' do + it 'does not convert ids given as fixnums to strings' do + client.group(data) + message = queue.pop(true) - it 'should not convert ids given as fixnums to strings' do - [:track, :screen, :page, :group, :identify, :alias].each do |s| - @client.send s, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" - message = @queue.pop(true) - check_property.call(message, :userId, 1) - check_property.call(message, :groupId, 2) - check_property.call(message, :previousId, 3) - check_property.call(message, :anonymousId, 4) + expect(check_property.call(message, :userId, 1)).to eq(true) + expect(check_property.call(message, :groupId, 2)).to eq(true) + end + end + + context 'alias' do + it 'does not convert ids given as fixnums to strings' do + client.alias(data) + message = queue.pop(true) + + expect(check_property.call(message, :userId, 1)).to eq(true) + expect(check_property.call(message, :previousId, 3)).to eq(true) end end - it 'should send integrations' do + it 'sends integrations' do [:track, :screen, :page, :group, :identify, :alias].each do |s| - @client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" - message = @queue.pop(true) - message[:integrations][:All].should be_true - message[:integrations][:Salesforce].should be_false + client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + message = queue.pop(true) + expect(message[:integrations][:All]).to eq(true) + expect(message[:integrations][:Salesforce]).to eq(false) end end end From 3160ac629d5ffac8e8f17b1139d3103ea5e360b9 Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Thu, 22 Oct 2015 23:06:28 -0700 Subject: [PATCH 124/322] Worker spec update to modern conventions Use expect instead of should Reword expectations to not include the word should --- spec/segment/analytics/worker_spec.rb | 44 +++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 11fa3f2..9c279e9 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -7,7 +7,7 @@ class Analytics it 'accepts string keys' do queue = Queue.new worker = Segment::Analytics::Worker.new(queue, 'secret', 'batch_size' => 100) - worker.instance_variable_get(:@batch_size).should == 100 + expect(worker.instance_variable_get(:@batch_size)).to eq(100) end end @@ -20,27 +20,29 @@ class Analytics Segment::Analytics::Defaults::Request::BACKOFF = 30.0 end - it 'should not error if the endpoint is unreachable' do - Net::HTTP.any_instance.stub(:post).and_raise(Exception) + it 'does not error if the endpoint is unreachable' do + expect do + Net::HTTP.any_instance.stub(:post).and_raise(Exception) - queue = Queue.new - queue << {} - worker = Segment::Analytics::Worker.new(queue, 'secret') - worker.run + queue = Queue.new + queue << {} + worker = Segment::Analytics::Worker.new(queue, 'secret') + worker.run - queue.should be_empty + expect(queue).to be_empty - Net::HTTP.any_instance.unstub(:post) + Net::HTTP.any_instance.unstub(:post) + end.to_not raise_error end - it 'should execute the error handler if the request is invalid' do + it 'executes the error handler if the request is invalid' do Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, "Some error")) on_error = Proc.new do |status, error| puts "#{status}, #{error}" end - on_error.should_receive(:call).once + expect(on_error).to receive(:call).once queue = Queue.new queue << {} @@ -49,46 +51,44 @@ class Analytics Segment::Analytics::Request::any_instance.unstub(:post) - queue.should be_empty + expect(queue).to be_empty end - it 'should not call on_error if the request is good' do - + it 'does not call on_error if the request is good' do on_error = Proc.new do |status, error| puts "#{status}, #{error}" end - on_error.should_receive(:call).at_most(0).times + expect(on_error).to_not receive(:call) queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new queue, 'testsecret', :on_error => on_error worker.run - queue.should be_empty + expect(queue).to be_empty end end describe '#is_requesting?' do - it 'should not return true if there isn\'t a current batch' do - + it 'does not return true if there isn\'t a current batch' do queue = Queue.new worker = Segment::Analytics::Worker.new(queue, 'testsecret') - worker.is_requesting?.should == false + expect(worker.is_requesting?).to eq(false) end - it 'should return true if there is a current batch' do + it 'returns true if there is a current batch' do queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') Thread.new do worker.run - worker.is_requesting?.should == false + expect(worker.is_requesting?).to eq(false) end - eventually { worker.is_requesting?.should be_true } + eventually { expect(worker.is_requesting?).to eq(true) } end end end From 23cb348b4a173d18844fa58753d16a90a8083fe6 Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Thu, 22 Oct 2015 23:10:01 -0700 Subject: [PATCH 125/322] Analytics spec update Conform to modern conventions with expect vs should Assert error is not raised explictly instead of an empty example Remove should from the assertion text --- spec/segment/analytics/client_spec.rb | 2 +- spec/segment/analytics_spec.rb | 69 ++++++++++++++++----------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 7c45d07..cde7e7a 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -113,7 +113,7 @@ class Analytics end.to_not raise_error end - it 'should convert time and date traits into iso8601 format' do + it 'converts time and date traits into iso8601 format' do client.identify({ :user_id => 'user', :traits => { diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index 3265a46..a2aa885 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -6,88 +6,99 @@ class Analytics let(:analytics) { Segment::Analytics.new :write_key => WRITE_KEY, :stub => true } describe '#track' do - it 'should error without an event' do + it 'errors without an event' do expect { analytics.track(:user_id => 'user') }.to raise_error(ArgumentError) end - it 'should error without a user_id' do + it 'errors without a user_id' do expect { analytics.track(:event => 'Event') }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - analytics.track Queued::TRACK - sleep(1) + it 'does not error with the required options' do + expect do + analytics.track Queued::TRACK + sleep(1) + end.to_not raise_error end end - describe '#identify' do - it 'should error without a user_id' do + it 'errors without a user_id' do expect { analytics.identify :traits => {} }.to raise_error(ArgumentError) end - it 'should not error with the required options' do + it 'does not error with the required options' do analytics.identify Queued::IDENTIFY sleep(1) end end describe '#alias' do - it 'should error without from' do + it 'errors without from' do expect { analytics.alias :user_id => 1234 }.to raise_error(ArgumentError) end - it 'should error without to' do + it 'errors without to' do expect { analytics.alias :previous_id => 1234 }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - analytics.alias ALIAS - sleep(1) + it 'does not error with the required options' do + expect do + analytics.alias ALIAS + sleep(1) + end.to_not raise_error end end describe '#group' do - it 'should error without group_id' do + it 'errors without group_id' do expect { analytics.group :user_id => 'foo' }.to raise_error(ArgumentError) end - it 'should error without user_id or anonymous_id' do + it 'errors without user_id or anonymous_id' do expect { analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - analytics.group Queued::GROUP - sleep(1) + it 'does not error with the required options' do + expect do + analytics.group Queued::GROUP + sleep(1) + end.to_not raise_error end end describe '#page' do - it 'should error without user_id or anonymous_id' do + it 'errors without user_id or anonymous_id' do expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - analytics.page Queued::PAGE - sleep(1) + it 'does not error with the required options' do + expect do + analytics.page Queued::PAGE + sleep(1) + end.to_not raise_error end end describe '#screen' do - it 'should error without user_id or anonymous_id' do + it 'errors without user_id or anonymous_id' do expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) end - it 'should not error with the required options' do - analytics.screen Queued::SCREEN - sleep(1) + it 'does not error with the required options' do + expect do + analytics.screen Queued::SCREEN + sleep(1) + end.to_not raise_error end end describe '#flush' do - it 'should flush without error' do - analytics.identify Queued::IDENTIFY - analytics.flush + it 'flushes without error' do + expect do + analytics.identify Queued::IDENTIFY + analytics.flush + end.to_not raise_error end end end From 707266e2dcd7d41b7d4727927f9d4454834ee049 Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Tue, 27 Oct 2015 22:41:14 -0700 Subject: [PATCH 126/322] Replace deprecated rpsec formatting option The previous format option "nested" has been replaced with "documentation" --- .rspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rspec b/.rspec index 7abe2a1..114486d 100644 --- a/.rspec +++ b/.rspec @@ -1 +1 @@ ---color --format nested \ No newline at end of file +--color --format documentation From d889ff689be81ca1e6df2fd1d3b5bd0c2e48b4fc Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Thu, 29 Oct 2015 22:28:19 -0700 Subject: [PATCH 127/322] Request Spec Added some heavy stubbing to Net::HTTP in order to test that the `Request` class responds appropriately. --- spec/segment/analytics/request_spec.rb | 191 +++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 spec/segment/analytics/request_spec.rb diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb new file mode 100644 index 0000000..40bdc3d --- /dev/null +++ b/spec/segment/analytics/request_spec.rb @@ -0,0 +1,191 @@ +require 'spec_helper' + +module Segment + class Analytics + describe Request do + before do + # Try and keep debug statements out of tests + allow(subject.logger).to receive(:error) + allow(subject.logger).to receive(:debug) + end + + describe '#initialize' do + let!(:net_http) { Net::HTTP.new(anything, anything) } + + before do + allow(Net::HTTP).to receive(:new) { net_http } + end + + it 'sets an initalized Net::HTTP read_timeout' do + expect(net_http).to receive(:use_ssl=) + described_class.new + end + + it 'sets an initalized Net::HTTP read_timeout' do + expect(net_http).to receive(:read_timeout=) + described_class.new + end + + it 'sets an initalized Net::HTTP open_timeout' do + expect(net_http).to receive(:open_timeout=) + described_class.new + end + + it 'sets the http client' do + expect(subject.instance_variable_get(:@http)).to_not be_nil + end + + context 'no options are set' do + it 'sets a default path' do + expect(subject.instance_variable_get(:@path)).to eq(described_class::PATH) + end + + it 'sets a default retries' do + expect(subject.instance_variable_get(:@retries)).to eq(described_class::RETRIES) + end + + it 'sets a default backoff' do + expect(subject.instance_variable_get(:@backoff)).to eq(described_class::BACKOFF) + end + + it 'initializes a new Net::HTTP with default host and port' do + expect(Net::HTTP).to receive(:new).with(described_class::HOST, described_class::PORT) + described_class.new + end + end + + context 'options are given' do + let(:path) { 'my/cool/path' } + let(:retries) { 1234 } + let(:backoff) { 10 } + let(:host) { 'http://www.example.com' } + let(:port) { 8080 } + let(:options) do + { + path: path, + retries: retries, + backoff: backoff, + host: host, + port: port + } + end + + subject { described_class.new(options) } + + it 'sets passed in path' do + expect(subject.instance_variable_get(:@path)).to eq(path) + end + + it 'sets passed in retries' do + expect(subject.instance_variable_get(:@retries)).to eq(retries) + end + + it 'sets passed in backoff' do + expect(subject.instance_variable_get(:@backoff)).to eq(backoff) + end + + it 'initializes a new Net::HTTP with passed in host and port' do + expect(Net::HTTP).to receive(:new).with(host, port) + described_class.new(options) + end + end + end + + describe '#post' do + let(:response) { Net::HTTPResponse.new(http_version, status_code, response_body) } + let(:http_version) { 1.1 } + let(:status_code) { 200 } + let(:response_body) { {}.to_json } + let(:write_key) { 'abcdefg' } + let(:batch) { [] } + + before do + allow(subject.instance_variable_get(:@http)).to receive(:request) { response } + allow(response).to receive(:body) { response_body } + end + + it 'initalizes a new Net::HTTP::Post with path and default headers' do + path = subject.instance_variable_get(:@path) + default_headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } + expect(Net::HTTP::Post).to receive(:new).with(path, default_headers).and_call_original + subject.post(write_key, batch) + end + + it 'adds basic auth to the Net::HTTP::Post' do + expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth).with(write_key, nil) + subject.post(write_key, batch) + end + + context 'with a stub' do + before do + allow(described_class).to receive(:stub) { true } + end + + it 'returns a 200 response' do + expect(subject.post(write_key, batch).status).to eq(200) + end + + it 'has a nil error' do + expect(subject.post(write_key, batch).error).to be_nil + end + + it 'logs a debug statement' do + expect(subject.logger).to receive(:debug).with(/stubbed request to/) + subject.post(write_key, batch) + end + end + + context 'a real request' do + context 'request is successful' do + let(:status_code) { 201 } + it 'returns a response code' do + expect(subject.post(write_key, batch).status).to eq(status_code) + end + + it 'returns a nil error' do + expect(subject.post(write_key, batch).error).to be_nil + end + end + + context 'request results in errorful response' do + let(:error) { 'this is an error' } + let(:response_body) { { error: error }.to_json } + + it 'returns the parsed error' do + expect(subject.post(write_key, batch).error).to eq(error) + end + end + + context 'request or parsing of response results in an exception' do + let(:response_body) { 'Malformed JSON ---' } + + let(:backoff) { 0 } + + subject { described_class.new(retries: retries, backoff: backoff) } + + context 'remaining retries is > 1' do + let(:retries) { 2 } + + it 'sleeps' do + expect(subject).to receive(:sleep).exactly(retries - 1).times + subject.post(write_key, batch) + end + end + + context 'remaining retries is 1' do + let(:retries) { 1 } + + it 'returns a -1 for status' do + expect(subject.post(write_key, batch).status).to eq(-1) + end + + it 'has a connection error' do + expect(subject.post(write_key, batch).error).to match(/Connection error/) + end + end + end + end + end + end + end +end From cbe5902b0a8fc6ac72c032e07bb43d3836669d80 Mon Sep 17 00:00:00 2001 From: Jake Yesbeck Date: Thu, 29 Oct 2015 22:34:38 -0700 Subject: [PATCH 128/322] Add basic Response Spec Not a complex spec, but valuable incase the model's contract changes --- spec/segment/analytics/response_spec.rb | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/segment/analytics/response_spec.rb diff --git a/spec/segment/analytics/response_spec.rb b/spec/segment/analytics/response_spec.rb new file mode 100644 index 0000000..7e09971 --- /dev/null +++ b/spec/segment/analytics/response_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +module Segment + class Analytics + describe Response do + describe '#status' do + it { expect(subject).to respond_to(:status) } + end + + describe '#error' do + it { expect(subject).to respond_to(:error) } + end + + describe '#initialize' do + let(:status) { 404 } + let(:error) { 'Oh No' } + + subject { described_class.new(status, error) } + + it 'sets the instance variable status' do + expect(subject.instance_variable_get(:@status)).to eq(status) + end + + it 'sets the instance variable error' do + expect(subject.instance_variable_get(:@error)).to eq(error) + end + end + end + end +end From eace66e75c015267b62a0f151c43661405e08469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eloy=20Dur=C3=A1n?= Date: Fri, 18 Dec 2015 13:53:44 +0100 Subject: [PATCH 129/322] Add support for optional explicit `messageId`. --- lib/segment/analytics/client.rb | 21 +++++++++++++++++++-- spec/segment/analytics/client_spec.rb | 11 ++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index b28f29d..138f819 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -52,6 +52,7 @@ def flush # :properties - Hash of event properties. (optional) # :timestamp - Time of when the event occurred. (optional) # :user_id - String of the user id. + # :message_id - String of the message id that uniquely identified a message across the API. (optional) def track attrs symbolize_keys! attrs check_user_id! attrs @@ -60,6 +61,7 @@ def track attrs properties = attrs[:properties] || {} timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] check_timestamp! timestamp @@ -76,10 +78,11 @@ def track attrs :event => event, :userId => attrs[:user_id], :anonymousId => attrs[:anonymous_id], - :context => context, + :context => context, :options => attrs[:options], :integrations => attrs[:integrations], :properties => properties, + :messageId => message_id, :timestamp => datetime_in_iso8601(timestamp), :type => 'track' }) @@ -95,6 +98,7 @@ def track attrs # :timestamp - Time of when the event occurred. (optional) # :traits - Hash of user traits. (optional) # :user_id - String of the user id + # :message_id - String of the message id that uniquely identified a message across the API. (optional) def identify attrs symbolize_keys! attrs check_user_id! attrs @@ -102,6 +106,7 @@ def identify attrs traits = attrs[:traits] || {} timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] check_timestamp! timestamp @@ -117,6 +122,7 @@ def identify attrs :context => context, :traits => traits, :options => attrs[:options], + :messageId => message_id, :timestamp => datetime_in_iso8601(timestamp), :type => 'identify' }) @@ -131,6 +137,7 @@ def identify attrs # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to + # :message_id - String of the message id that uniquely identified a message across the API. (optional) def alias(attrs) symbolize_keys! attrs @@ -138,6 +145,7 @@ def alias(attrs) to = attrs[:user_id] timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] check_presence! from, 'previous_id' check_presence! to, 'user_id' @@ -150,6 +158,7 @@ def alias(attrs) :integrations => attrs[:integrations], :context => context, :options => attrs[:options], + :messageId => message_id, :timestamp => datetime_in_iso8601(timestamp), :type => 'alias' }) @@ -164,6 +173,7 @@ def alias(attrs) # :previous_id - String of the id to alias from # :timestamp - Time of when the alias occured (optional) # :user_id - String of the id to alias to + # :message_id - String of the message id that uniquely identified a message across the API. (optional) def group(attrs) symbolize_keys! attrs check_user_id! attrs @@ -173,6 +183,7 @@ def group(attrs) traits = attrs[:traits] || {} timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash isoify_dates! traits @@ -188,6 +199,7 @@ def group(attrs) :integrations => attrs[:integrations], :options => attrs[:options], :context => context, + :messageId => message_id, :timestamp => datetime_in_iso8601(timestamp), :type => 'group' }) @@ -205,6 +217,7 @@ def group(attrs) # :properties - Hash of page properties (optional) # :timestamp - Time of when the pageview occured (optional) # :user_id - String of the id to alias from + # :message_id - String of the message id that uniquely identified a message across the API. (optional) def page(attrs) symbolize_keys! attrs check_user_id! attrs @@ -213,6 +226,7 @@ def page(attrs) properties = attrs[:properties] || {} timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties @@ -229,6 +243,7 @@ def page(attrs) :integrations => attrs[:integrations], :options => attrs[:options], :context => context, + :messageId => message_id, :timestamp => datetime_in_iso8601(timestamp), :type => 'page' }) @@ -253,6 +268,7 @@ def screen(attrs) properties = attrs[:properties] || {} timestamp = attrs[:timestamp] || Time.new context = attrs[:context] || {} + message_id = attrs[:message_id].to_s if attrs[:message_id] fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties @@ -269,6 +285,7 @@ def screen(attrs) :options => attrs[:options], :integrations => attrs[:integrations], :context => context, + :messageId => message_id, :timestamp => timestamp.iso8601, :type => 'screen' }) @@ -288,7 +305,7 @@ def queued_messages # returns Boolean of whether the item was added to the queue. def enqueue(action) # add our request id for tracing purposes - action[:messageId] = uid + action[:messageId] ||= uid unless queue_full = @queue.length >= @max_queue_size ensure_worker_running @queue << action diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index cde7e7a..251962b 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -256,7 +256,7 @@ class Analytics context 'common' do check_property = proc { |msg, k, v| msg[k] && msg[k] == v } - let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" } } + let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => "coco barked", :name => "coco" } } it 'does not convert ids given as fixnums to strings' do [:track, :screen, :page, :identify].each do |s| @@ -268,6 +268,15 @@ class Analytics end end + it 'converts message id to string' do + [:track, :screen, :page, :group, :identify, :alias].each do |s| + client.send(s, data) + message = queue.pop(true) + + expect(check_property.call(message, :messageId, '5')).to eq(true) + end + end + context 'group' do it 'does not convert ids given as fixnums to strings' do client.group(data) From 6b932bedc884260f1f7007d5cf0c2f096559c40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eloy=20Dur=C3=A1n?= Date: Fri, 18 Dec 2015 14:56:37 +0100 Subject: [PATCH 130/322] Do not needlessly override request stubbing. --- lib/segment/analytics.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index f47293e..78d6c28 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -10,7 +10,7 @@ module Segment class Analytics def initialize options = {} - Request.stub = options[:stub] + Request.stub = options[:stub] if options.has_key?(:stub) @client = Segment::Analytics::Client.new options end From beb09920fdad75b7079d3ac80a9060f2432a4bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eloy=20Dur=C3=A1n?= Date: Fri, 18 Dec 2015 22:54:43 +0100 Subject: [PATCH 131/322] Ensure the error handler is called before Client#flush finishes. --- lib/segment/analytics/worker.rb | 4 ++-- spec/segment/analytics/worker_spec.rb | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index dd244b6..9288c85 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -44,9 +44,9 @@ def run res = Request.new.post @write_key, @batch - @lock.synchronize { @batch.clear } - @on_error.call res.status, res.error unless res.status == 200 + + @lock.synchronize { @batch.clear } end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 9c279e9..1accd22 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -11,7 +11,7 @@ class Analytics end end - describe '#flush' do + describe '#run' do before :all do Segment::Analytics::Defaults::Request::BACKOFF = 0.1 end @@ -35,23 +35,29 @@ class Analytics end.to_not raise_error end - it 'executes the error handler if the request is invalid' do + it 'executes the error handler, before the request phase ends, if the request is invalid' do Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, "Some error")) - on_error = Proc.new do |status, error| - puts "#{status}, #{error}" + status = error = nil + on_error = Proc.new do |yielded_status, yielded_error| + sleep 0.2 # Make this take longer than thread spin-up (below) + status, error = yielded_status, yielded_error end - expect(on_error).to receive(:call).once - queue = Queue.new queue << {} worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error - worker.run + + # This is to ensure that Client#flush doesn’t finish before calling the error handler. + Thread.new { worker.run } + sleep 0.1 # First give thread time to spin-up. + sleep 0.01 while worker.is_requesting? Segment::Analytics::Request::any_instance.unstub(:post) expect(queue).to be_empty + expect(status).to eq(400) + expect(error).to eq('Some error') end it 'does not call on_error if the request is good' do From a553333fa2510ec80b741075d2c01a246a894651 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Mon, 25 Apr 2016 11:18:56 -0400 Subject: [PATCH 132/322] Add missing history for 2.0.13 --- History.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/History.md b/History.md index cbdb33f..791c719 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,11 @@ +2.0.13 / 2015-09-15 +================== + + * readme: updated install docs + * fix: page/screen to allow no name + * git: ignore ruby version + * travis-ci: remove old rubys + 2.0.12 / 2015-01-10 ================== From 1d18707c7144fc577e2dfd2d69cbd133d11d641b Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Fri, 17 Jun 2016 17:47:42 +0530 Subject: [PATCH 133/322] Release version 2.1.0 --- History.md | 7 +++++++ Makefile | 2 +- lib/segment/analytics/version.rb | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 791c719..fc50ada 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,10 @@ + +2.1.0 / 2016-06-17 +================== + + * Fix: Ensure error handler is called before Client#flush finishes. + * Feature: Support setting a custom message ID. + 2.0.13 / 2015-09-15 ================== diff --git a/Makefile b/Makefile index dc6ade0..5082529 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,4 @@ test: build: gem build ./analytics-ruby.gemspec -.PHONY: test \ No newline at end of file +.PHONY: test build diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index f711ab0..1df6de5 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.0.13' + VERSION = '2.1.0' end end From 56098c1f371416af1ffdb26e9ac11f02094fbb90 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Fri, 17 Jun 2016 18:18:46 +0530 Subject: [PATCH 134/322] Add RELEASING.md --- RELEASING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..0edd12b --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,10 @@ +Releasing +========= + + 1. Verify everything works with `make test build`. + 2. Bump version in [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). + 3. Update [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). + 4. Commit and tag `git commit -am "Release {version}" && git tag -a {version} -m "Version {version}"`. + 5. Build the gem with the tagged version `make build`. + 6. Upload to RubyGems with `gem push analytics-ruby-{version}.gem`. + 7. Upload to Github with `git push -u origin master && git push --tags`. From 74459e753f91818fe551b1f2e59b4e468b848f53 Mon Sep 17 00:00:00 2001 From: Ladan Date: Thu, 14 Jul 2016 17:42:01 -0700 Subject: [PATCH 135/322] Adding an (experimental) CLI --- bin/analytics.rb | 152 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100755 bin/analytics.rb diff --git a/bin/analytics.rb b/bin/analytics.rb new file mode 100755 index 0000000..e7543cb --- /dev/null +++ b/bin/analytics.rb @@ -0,0 +1,152 @@ +#!/usr/bin/env ruby + +require 'segment/analytics' +require 'rubygems' +require 'commander/import' +require 'time' +require 'json' + +program :name, 'simulator.rb' +program :version, '0.0.1' +program :description, 'scripting simulator' + +# use an env var for write key, instead of a flag +Analytics = Segment::Analytics.new({ + write_key: ENV['SEGMENT_WRITE_KEY'], + on_error: Proc.new { |status, msg| print msg } +}) + +def toObject(str) + return JSON.parse(str) +end + +# high level +# analytics [options] +# SEGMENT_WRITE_KEY= ./analytics.rb [options] +# SEGMENT_WRITE_KEY= ./analytics.rb track --event testing --user 1234 --anonymous 567 --properties '{"hello": "goodbye"}' --context '{"slow":"poke"}' + + + +# track +command :track do |c| + c.description = 'track a user event' + c.option '--user ', String, 'the user id to send the event as' + c.option '--event ', String, 'the event name to send with the event' + c.option '--anonymous ', String, 'the anonymous user id to send the event as' + c.option '--properties ', 'the event properties to send (JSON-encoded)' + c.option '--context ', 'additional context for the event (JSON-encoded)' + + c.action do |args, options| + Analytics.track({ + user_id: options.user, + event: options.event, + anonymous_id: options.anonymous, + properties: toObject(options.properties), + context: toObject(options.context) + }) + Analytics.flush + end + +end + +# page +command :page do |c| + c.description = 'track a page view' + c.option '--user ', String, 'the user id to send the event as' + c.option '--anonymous ', String, 'the anonymous user id to send the event as' + c.option '--name ', String, 'the page name' + c.option '--properties ', 'the event properties to send (JSON-encoded)' + c.option '--context ', 'additional context for the event (JSON-encoded)' + c.option '--category ', 'the category of the page' + c.action do |args, options| + Analytics.page({ + user_id: options.user, + anonymous_id: options.anonymous, + name: options.name, + properties: toObject(options.properties), + context: toObject(options.context), + category: options.category + }) + Analytics.flush + end + +end + +# identify +command :identify do |c| + c.description = 'identify a user' + c.option '--user ', String, 'the user id to send the event as' + c.option '--anonymous ', String, 'the anonymous user id to send the event as' + c.option '--traits ', String, 'the user traits to send (JSON-encoded)' + c.option '--context ', 'additional context for the event (JSON-encoded)' + c.action do |args, options| + Analytics.identify({ + user_id: options.user, + anonymous_id: options.anonymous, + traits: toObject(options.traits), + context: toObject(options.context) + }) + Analytics.flush + end + +end + +# screen +command :screen do |c| + c.description = 'track a screen view' + c.option '--user ', String, 'the user id to send the event as' + c.option '--anonymous ', String, 'the anonymous user id to send the event as' + c.option '--name ', String, 'the screen name' + c.option '--properties ', String, 'the event properties to send (JSON-encoded)' + c.option '--context ', 'additional context for the event (JSON-encoded)' + c.action do |args, options| + Analytics.identify({ + user_id: options.user, + anonymous_id: options.anonymous, + name: option.name, + traits: toObject(options.traits), + properties: toObject(option.properties), + context: toObject(options.context) + }) + Analytics.flush + end + +end + + +# group +command :group do |c| + c.description = 'identify a group of users' + c.option '--user ', String, 'the user id to send the event as' + c.option '--anonymous ', String, 'the anonymous user id to send the event as' + c.option '--group ', String, 'the group id to associate this user with' + c.option '--traits ', String, 'attributes about the group (JSON-encoded)' + c.option '--context ', 'additional context for the event (JSON-encoded)' + c.action do |args, options| + Analytics.group({ + user_id: options.user, + anonymous_id: options.anonymous, + group_id: options.group, + traits: toObject(options.traits), + context: toObject(options.context) + }) + Analytics.flush + end + +end + +# alias +command :alias do |c| + c.description = 'remap a user to a new id' + c.option '--user ', String, 'the user id to send the event as' + c.option '--previous ', String, 'the previous user id (to add the alias for)' + c.action do |args, options| + Analytics.alias({ + user_id: options.user, + previous_id: options.previous + }) + Analytics.flush + end + +end + From a2182800017963efcad48508e204216982bfaf13 Mon Sep 17 00:00:00 2001 From: Teresa Nesteby Date: Wed, 3 Aug 2016 12:53:22 -0700 Subject: [PATCH 136/322] updates link to ruby docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9087d27..7e1e1c4 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Refer to the section below for documenation on individual available calls. ## Documentation -Documentation is available at [segment.com/libraries/ruby](https://segment.com/libraries/ruby) +Documentation is available at [segment.com/docs/sources/server/ruby](https://segment.com/docs/sources/server/ruby/) ## Testing From fb970a55de31f0fa6bf2dfc6bdf1b0ed2da811ab Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 20:27:28 -0700 Subject: [PATCH 137/322] rename bin/analytics.rb to bin/analytics --- bin/{analytics.rb => analytics} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/{analytics.rb => analytics} (100%) diff --git a/bin/analytics.rb b/bin/analytics similarity index 100% rename from bin/analytics.rb rename to bin/analytics From 8dca20ade8338074f225ef46470a0f7cddeed805 Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 20:27:36 -0700 Subject: [PATCH 138/322] adding bindir to gem config --- analytics-ruby.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 854557e..b7eb3f6 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -5,6 +5,7 @@ Gem::Specification.new do |spec| spec.version = Segment::Analytics::VERSION spec.files = Dir.glob('**/*') spec.require_paths = ['lib'] + spec.bindir = 'bin' spec.summary = 'Segment.io analytics library' spec.description = 'The Segment.io ruby analytics library' spec.authors = ['Segment.io'] From 4d2fa8726b089f85a54755b03ed27032a4f3736e Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 20:29:38 -0700 Subject: [PATCH 139/322] Release 2.2.0 --- History.md | 5 +++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index fc50ada..221e411 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +2.2.0 / 2016-08-03 +================== + + * Adding an (experimental) CLI + 2.1.0 / 2016-06-17 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 1df6de5..d768eb3 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.1.0' + VERSION = '2.2.0' end end From 94d3be400903a7c09a583de317a888df99fd759b Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 20:51:37 -0700 Subject: [PATCH 140/322] add executables to spec --- analytics-ruby.gemspec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index b7eb3f6..59d48e5 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -1,11 +1,12 @@ require File.expand_path('../lib/segment/analytics/version', __FILE__) Gem::Specification.new do |spec| - spec.name = 'analytics-ruby' + spec.name = 'analytics-ruby' spec.version = Segment::Analytics::VERSION - spec.files = Dir.glob('**/*') + spec.files = Dir.glob('**/*') spec.require_paths = ['lib'] spec.bindir = 'bin' + spec.executables = ['analytics'] spec.summary = 'Segment.io analytics library' spec.description = 'The Segment.io ruby analytics library' spec.authors = ['Segment.io'] From 2e97a4effba7f7a7c59fde4e9281e56b054ae2d4 Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 20:51:47 -0700 Subject: [PATCH 141/322] Release 2.2.1 --- History.md | 5 +++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 221e411..4507c27 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +2.2.1 / 2016-08-03 +================== + + * add executables to spec + 2.2.0 / 2016-08-03 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index d768eb3..b408944 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.0' + VERSION = '2.2.1' end end From e4715d29aecb3d7158a7257119a91de96a419f0f Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 23:00:27 -0700 Subject: [PATCH 142/322] adding commander as dep (for CLI) --- analytics-ruby.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 59d48e5..416bf16 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |spec| # Ruby 1.8 requires json spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" + spec.add_dependency 'commander', '~> 4.4' spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'wrong', '~> 0.0' From 0aea71473fbdcf55972c3d1fdde988f969b0d040 Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Wed, 3 Aug 2016 23:00:48 -0700 Subject: [PATCH 143/322] Release 2.2.2 --- History.md | 5 +++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4507c27..c188aed 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +2.2.2 / 2016-08-03 +================== + + * adding commander as dep (for CLI) + 2.2.1 / 2016-08-03 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index b408944..939d338 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.1' + VERSION = '2.2.2' end end From f2ead8576a97bc6e711cbcbf47f4d685456e657e Mon Sep 17 00:00:00 2001 From: Benjamin Hoffman Date: Wed, 22 Mar 2017 11:56:24 -0700 Subject: [PATCH 144/322] Enforces userId anonId requirement --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 138f819..f920ee5 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -355,7 +355,7 @@ def event attrs end def check_user_id! attrs - fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] + fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id].present? || attrs[:anonymous_id].present? end def ensure_worker_running From 74597d338b2662aec5f71300f97c9fb9c3f7713a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 22 Mar 2017 20:46:42 -0700 Subject: [PATCH 145/322] Adds spec --- spec/segment/analytics/client_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 251962b..8befd62 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -99,6 +99,10 @@ class Analytics expect { client.identify({}) }.to raise_error(ArgumentError) end + it 'errors on an empty string' do + expect { client.identify({ :user_id => '' }) }.to raise_error(ArgumentError) + end + it 'does not error with the required options' do expect do client.identify Queued::IDENTIFY From 4cb5548b68bd9533835fe33aa76ff0f4dcb51602 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Wed, 12 Apr 2017 18:46:58 +0530 Subject: [PATCH 146/322] removed gem wrong and created helper for method --- analytics-ruby.gemspec | 1 - spec/spec_helper.rb | 27 ++++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 416bf16..1c18d8a 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -19,7 +19,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'commander', '~> 4.4' spec.add_development_dependency 'rake', '~> 10.3' - spec.add_development_dependency 'wrong', '~> 0.0' spec.add_development_dependency 'rspec', '~> 2.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5fc2ff7..420be8a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,6 @@ require 'segment/analytics' -require 'wrong' require 'active_support/time' -include Wrong - # Setting timezone for ActiveSupport::TimeWithZone to UTC Time.zone = 'UTC' @@ -79,3 +76,27 @@ module Requested end end end + +# usage: +# it "should return a result of 5" do +# eventually(options: {timeout: 1}) { long_running_thing.result.should eq(5) } +# end + +module AsyncHelper + def eventually(options: {}) + timeout = options[:timeout] || 2 + interval = options[:interval] || 0.1 + time_limit = Time.now + timeout + loop do + begin + yield + rescue => error + end + return if error.nil? + raise error if Time.now >= time_limit + sleep interval + end + end +end + +include AsyncHelper \ No newline at end of file From 7dcb4026c6f3b6fa1f23420ee2bad28882c24880 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Wed, 12 Apr 2017 19:59:07 +0530 Subject: [PATCH 147/322] fix error in jruby --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 420be8a..ea815ae 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -83,7 +83,7 @@ module Requested # end module AsyncHelper - def eventually(options: {}) + def eventually(options = {}) timeout = options[:timeout] || 2 interval = options[:interval] || 0.1 time_limit = Time.now + timeout From 6b96679687f50c8e3d647682746ee6db1ac323cf Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Wed, 12 Apr 2017 20:18:17 +0530 Subject: [PATCH 148/322] installing latest version of bunlder in travis to fix error in 1.9.3 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1a02c9f..c63d1b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,6 @@ rvm: - 1.9.3 - 2.0.0 - 2.1.0 + +before_install: + - gem install bundler \ No newline at end of file From cbd0fabdf6f502b5f78317f7f1ed210620d31477 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Wed, 12 Apr 2017 23:53:44 +0530 Subject: [PATCH 149/322] changing default timeout and default interval value in Asynchelper#eventually --- spec/spec_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ea815ae..024f380 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -84,8 +84,8 @@ module Requested module AsyncHelper def eventually(options = {}) - timeout = options[:timeout] || 2 - interval = options[:interval] || 0.1 + timeout = options[:timeout] || 5 #seconds + interval = options[:interval] || 0.25 #seconds time_limit = Time.now + timeout loop do begin From 90c7826b533f8c83b242aae3f4a61ed3def729e9 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Thu, 13 Apr 2017 11:49:25 +0530 Subject: [PATCH 150/322] fix failing test of wroker spec. Used present? method to check batch is currently processing. --- lib/segment/analytics/worker.rb | 4 +--- spec/segment/analytics/worker_spec.rb | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 9288c85..90d21a2 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -41,11 +41,9 @@ def run @batch << @queue.pop end end - res = Request.new.post @write_key, @batch @on_error.call res.status, res.error unless res.status == 200 - @lock.synchronize { @batch.clear } end end @@ -53,7 +51,7 @@ def run # public: Check whether we have outstanding requests. # def is_requesting? - @lock.synchronize { !@batch.empty? } + @lock.synchronize { @batch.present? } end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 1accd22..766bb43 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -85,16 +85,18 @@ class Analytics end it 'returns true if there is a current batch' do + queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') Thread.new do worker.run - expect(worker.is_requesting?).to eq(false) + expect(worker.is_requesting?).to eq(true) end - eventually { expect(worker.is_requesting?).to eq(true) } + eventually { expect(worker.is_requesting?).to eq(false) } + end end end From bba8c7f4fafa25f1fb2e31445921343c457775b9 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Thu, 13 Apr 2017 20:30:03 +0530 Subject: [PATCH 151/322] adding comment for race condition and fix suggested changes --- lib/segment/analytics/worker.rb | 3 ++- spec/segment/analytics/worker_spec.rb | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 90d21a2..d105bc2 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -41,6 +41,7 @@ def run @batch << @queue.pop end end + res = Request.new.post @write_key, @batch @on_error.call res.status, res.error unless res.status == 200 @@ -51,7 +52,7 @@ def run # public: Check whether we have outstanding requests. # def is_requesting? - @lock.synchronize { @batch.present? } + @lock.synchronize { @batch.any? } end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 766bb43..de7839b 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -85,7 +85,6 @@ class Analytics end it 'returns true if there is a current batch' do - queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') From 949612e93475da5d30d5ab60dca31c82e02f4b47 Mon Sep 17 00:00:00 2001 From: Anand Soni Date: Thu, 13 Apr 2017 20:38:18 +0530 Subject: [PATCH 152/322] adding comment to worker spec about race condition --- spec/segment/analytics/worker_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index de7839b..ad879d8 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -84,6 +84,12 @@ class Analytics expect(worker.is_requesting?).to eq(false) end + # Race Condition + # When we call worker.ran it creates new Request object and + # do post request. So we expect to check a batch is running. + # In eventually function we check for 5 seconds that + # all batches finished its process. + it 'returns true if there is a current batch' do queue = Queue.new queue << Requested::TRACK From cedf380eb90407471e47230281f7154796c4655f Mon Sep 17 00:00:00 2001 From: Benjamin Hoffman Date: Fri, 14 Apr 2017 00:22:38 -0700 Subject: [PATCH 153/322] adds spec for anonymous_id --- spec/segment/analytics/client_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 8befd62..c41a7e6 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -99,6 +99,10 @@ class Analytics expect { client.identify({}) }.to raise_error(ArgumentError) end + it 'errors on an empty anonymous_id' do + expect { client.identify({ :anonymous_id => '' }) }.to raise_error(ArgumentError) + end + it 'errors on an empty string' do expect { client.identify({ :user_id => '' }) }.to raise_error(ArgumentError) end From 675867197db193a6839465693272cab669935701 Mon Sep 17 00:00:00 2001 From: Benjamin Hoffman Date: Fri, 14 Apr 2017 00:49:02 -0700 Subject: [PATCH 154/322] run spec --- spec/segment/analytics/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index c41a7e6..94ba084 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -103,7 +103,7 @@ class Analytics expect { client.identify({ :anonymous_id => '' }) }.to raise_error(ArgumentError) end - it 'errors on an empty string' do + it 'errors on an empty user_id' do expect { client.identify({ :user_id => '' }) }.to raise_error(ArgumentError) end From 4f59494c3b07551d4e2ee0070b6daa08cee21651 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 8 May 2017 22:07:30 -0300 Subject: [PATCH 155/322] Update readme (#89) * Fix typo and split sample use into different code blocks --- README.md | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 7e1e1c4..c7c07fd 100644 --- a/README.md +++ b/README.md @@ -8,50 +8,43 @@ analytics-ruby is a ruby client for [Segment](https://segment.com) ## Install Into Gemfile from rubygems.org: -``` -gem 'analytics-ruby', :require => "segment" +```ruby +gem 'analytics-ruby', require: "segment" ``` Into environment gems from rubygems.org: -``` +```ruby gem install 'analytics-ruby' ``` ## Usage Create an instance of the Analytics object: +```ruby +analytics = Segment::Analytics.new(write_key: 'YOUR_WRITE_KEY') ``` -analytics = Segment::Analytics.new({ - write_key: 'YOUR_WRITE_KEY' -}) + +Identify the user for the people section, see more [here](https://segment.com/docs/libraries/ruby/#identify). +```ruby +analytics.identify(user_id: 42, + traits: { + email: 'name@example.com', + first_name: 'Foo', + last_name: 'Bar' + }) ``` -Sample usage: +Alias an user, see more [here](https://segment.com/docs/libraries/ruby/#alias). +```ruby +analytics.alias(user_id: 41) ``` -user = User.last - -# Identify the user for the people section -analytics.identify( - { - user_id: user.id, - traits: { - email: user.email, - first_name: user.first_name, - last_name: user.last_name - } - } -) - -# Track a user event -analytics.track( - { - user_id: user.id, - event: 'Created Account' - } -) + +Track a user event, see more [here](https://segment.com/docs/libraries/ruby/#track). +```ruby +analytics.track(user_id: 42, event: 'Created Account') ``` -Refer to the section below for documenation on individual available calls. +There are a few calls available, please check the documentation section. ## Documentation From 7b71cb37fa676e52b8402ce768925b052135e27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BAnera=20S=C3=A1nchez?= Date: Thu, 14 Sep 2017 20:49:37 -0500 Subject: [PATCH 156/322] implement respond_to_missing? instead of respond_to? (#120) --- lib/segment/analytics.rb | 2 +- spec/segment/analytics_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 78d6c28..da0b400 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -22,7 +22,7 @@ def method_missing message, *args, &block end end - def respond_to? method_name, include_private = false + def respond_to_missing?(method_name, include_private = false) @client.respond_to?(method_name) || super end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index a2aa885..bc06703 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -101,6 +101,20 @@ class Analytics end.to_not raise_error end end + + describe '#respond_to?' do + it 'responds to all public instance methods of Segment::Analytics::Client' do + expect(analytics).to respond_to(*Segment::Analytics::Client.public_instance_methods(false)) + end + end + + describe '#method' do + Segment::Analytics::Client.public_instance_methods(false).each do |public_method| + it "returns a Method object with '#{public_method}' as argument" do + expect(analytics.method(public_method).class).to eq(Method) + end + end + end end end end From 4cc38b9c1ed79c86233c638f41bc52e2fd3574bd Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Thu, 14 Sep 2017 21:14:15 -0700 Subject: [PATCH 157/322] Revert "Enforces userId anonId requirement" --- lib/segment/analytics/client.rb | 2 +- spec/segment/analytics/client_spec.rb | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index f920ee5..138f819 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -355,7 +355,7 @@ def event attrs end def check_user_id! attrs - fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id].present? || attrs[:anonymous_id].present? + fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] end def ensure_worker_running diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 94ba084..251962b 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -99,14 +99,6 @@ class Analytics expect { client.identify({}) }.to raise_error(ArgumentError) end - it 'errors on an empty anonymous_id' do - expect { client.identify({ :anonymous_id => '' }) }.to raise_error(ArgumentError) - end - - it 'errors on an empty user_id' do - expect { client.identify({ :user_id => '' }) }.to raise_error(ArgumentError) - end - it 'does not error with the required options' do expect do client.identify Queued::IDENTIFY From 6fc06965905a62937e1cb4cf3a124b2dfa7afdbc Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Thu, 14 Sep 2017 21:15:56 -0700 Subject: [PATCH 158/322] Revert "Fix failing test in worker spec" --- lib/segment/analytics/worker.rb | 3 ++- spec/segment/analytics/worker_spec.rb | 11 ++--------- spec/spec_helper.rb | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index d105bc2..9288c85 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -45,6 +45,7 @@ def run res = Request.new.post @write_key, @batch @on_error.call res.status, res.error unless res.status == 200 + @lock.synchronize { @batch.clear } end end @@ -52,7 +53,7 @@ def run # public: Check whether we have outstanding requests. # def is_requesting? - @lock.synchronize { @batch.any? } + @lock.synchronize { !@batch.empty? } end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index ad879d8..1accd22 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -84,12 +84,6 @@ class Analytics expect(worker.is_requesting?).to eq(false) end - # Race Condition - # When we call worker.ran it creates new Request object and - # do post request. So we expect to check a batch is running. - # In eventually function we check for 5 seconds that - # all batches finished its process. - it 'returns true if there is a current batch' do queue = Queue.new queue << Requested::TRACK @@ -97,11 +91,10 @@ class Analytics Thread.new do worker.run - expect(worker.is_requesting?).to eq(true) + expect(worker.is_requesting?).to eq(false) end - eventually { expect(worker.is_requesting?).to eq(false) } - + eventually { expect(worker.is_requesting?).to eq(true) } end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 024f380..ea815ae 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -84,8 +84,8 @@ module Requested module AsyncHelper def eventually(options = {}) - timeout = options[:timeout] || 5 #seconds - interval = options[:interval] || 0.25 #seconds + timeout = options[:timeout] || 2 + interval = options[:interval] || 0.1 time_limit = Time.now + timeout loop do begin From 246e8bac180da18159c1c06079b36a4899115b3a Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Thu, 14 Sep 2017 21:25:26 -0700 Subject: [PATCH 159/322] Release 2.2.3.pre --- History.md | 6 ++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c188aed..7dfa15c 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +2.2.3.pre / 2017-09-14 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/120): Override `respond_to_missing` instead of `respond_to?` to facilitate mock the library in tests. + + 2.2.2 / 2016-08-03 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 939d338..df7b3d0 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.2' + VERSION = '2.2.3.pre' end end From fb3d56a9ea22a596ca68adec67154c6c0b803ff0 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Fri, 1 Sep 2017 15:06:54 -0700 Subject: [PATCH 160/322] Update platforms to test against on CI. * Use latest 2.1 release (2.1.10). * Add latest 2.2, 2.3 and 2.4 releases. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c63d1b9..0638b59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,10 @@ rvm: - jruby-19mode - 1.9.3 - 2.0.0 - - 2.1.0 + - 2.1.10 + - 2.2.8 + - 2.3.5 + - 2.4.2 before_install: - - gem install bundler \ No newline at end of file + - gem install bundler From 3fefcf0274508abcd20de02807c904883931b9a4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 21 Nov 2017 11:51:24 +0530 Subject: [PATCH 161/322] Update rspec to ~> 3.0 --- analytics-ruby.gemspec | 2 +- spec/spec_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 1c18d8a..2df0d8a 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'commander', '~> 4.4' spec.add_development_dependency 'rake', '~> 10.3' - spec.add_development_dependency 'rspec', '~> 2.0' + spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ea815ae..2e06002 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -90,7 +90,7 @@ def eventually(options = {}) loop do begin yield - rescue => error + rescue RSpec::Expectations::ExpectationNotMetError => error end return if error.nil? raise error if Time.now >= time_limit @@ -99,4 +99,4 @@ def eventually(options = {}) end end -include AsyncHelper \ No newline at end of file +include AsyncHelper From e152c6b82ac1f85f0188b0ba0b3d2bf572c8e0bb Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 21 Nov 2017 15:33:17 +0530 Subject: [PATCH 162/322] Update ActiveSupport ActiveSupport is used as a testing dependency to ensure that ActiveSupport::TimeWithZone objects are converted to iso8601 format correctly. Because of recent changes in ActiveSupport, there is a change in the way `DateTime`s are converted to `Time`s, specs have been changed to reflect that. Reference: https://github.com/rails/rails/commit/b79adc4323ff289aed3f5787fdfbb9542aa4f89f --- analytics-ruby.gemspec | 2 +- spec/segment/analytics/client_spec.rb | 33 +++++++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 2df0d8a..d00fe5a 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -21,5 +21,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' - spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0' + spec.add_development_dependency 'activesupport', '~> 4.1.11' end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 251962b..d39dc7a 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -85,11 +85,12 @@ class Analytics }) message = queue.pop - expect(message[:properties][:time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:properties][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:properties][:date_time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:properties][:date]).to eq('2013-01-01') - expect(message[:properties][:nottime]).to eq('x') + properties = message[:properties] + expect(properties[:time]).to eq('2013-01-01T00:00:00.000Z') + expect(properties[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(properties[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') + expect(properties[:date]).to eq('2013-01-01') + expect(properties[:nottime]).to eq('x') end end @@ -127,11 +128,12 @@ class Analytics message = queue.pop - expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:date]).to eq('2013-01-01') - expect(message[:traits][:nottime]).to eq('x') + traits = message[:traits] + expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z') + expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') + expect(traits[:date]).to eq('2013-01-01') + expect(traits[:nottime]).to eq('x') end end @@ -191,11 +193,12 @@ class Analytics message = queue.pop - expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z') - expect(message[:traits][:date]).to eq('2013-01-01') - expect(message[:traits][:nottime]).to eq('x') + traits = message[:traits] + expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z') + expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') + expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') + expect(traits[:date]).to eq('2013-01-01') + expect(traits[:nottime]).to eq('x') end end From 0388eea671bb706bea82f17725433bc783c11e30 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 21 Nov 2017 17:55:35 +0530 Subject: [PATCH 163/322] When testing client, force worker to not pick jobs When calling `enqueue` on the client, it automatically spawns a worker thread to consume items from the queue. This causes race conditions between the tests and worker picking items from `queue`. --- spec/segment/analytics/client_spec.rb | 29 +++++++++++++++------------ spec/spec_helper.rb | 7 +++++++ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index d39dc7a..89cde66 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -3,7 +3,12 @@ module Segment class Analytics describe Client do - let(:client) { Client.new :write_key => WRITE_KEY } + let(:client) do + Client.new(:write_key => WRITE_KEY).tap { |client| + # Ensure that worker doesn't consume items from the queue + client.instance_variable_set(:@worker, NoopWorker.new) + } + end let(:queue) { client.instance_variable_get :@queue } describe '#initialize' do @@ -158,10 +163,6 @@ class Analytics end describe '#group' do - after do - client.flush - end - it 'errors without group_id' do expect { client.group :user_id => 'foo' }.to raise_error(ArgumentError) end @@ -235,21 +236,23 @@ class Analytics end describe '#flush' do + let(:client_with_worker) { Client.new(:write_key => WRITE_KEY) } + it 'waits for the queue to finish on a flush' do - client.identify Queued::IDENTIFY - client.track Queued::TRACK - client.flush + client_with_worker.identify Queued::IDENTIFY + client_with_worker.track Queued::TRACK + client_with_worker.flush - expect(client.queued_messages).to eq(0) + expect(client_with_worker.queued_messages).to eq(0) end it 'completes when the process forks' do - client.identify Queued::IDENTIFY + client_with_worker.identify Queued::IDENTIFY Process.fork do - client.track Queued::TRACK - client.flush - expect(client.queued_messages).to eq(0) + client_with_worker.track Queued::TRACK + client_with_worker.flush + expect(client_with_worker.queued_messages).to eq(0) end Process.wait diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2e06002..e67ca5d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -77,6 +77,13 @@ module Requested end end +# A worker that doesn't consume jobs +class NoopWorker + def run + # Does nothing + end +end + # usage: # it "should return a result of 5" do # eventually(options: {timeout: 1}) { long_running_thing.result.should eq(5) } From 3c44ee9ffb7543c4779800e605877f10301266d7 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 29 Nov 2017 22:44:17 +0530 Subject: [PATCH 164/322] Add linter (#130) * Add rubocop (style linter), run as part of default rake task * Generate rubocop_todo.yml * Autocorrect simple cops * Use consistent style for indenting hashes * Auto-correct cops * Avoid redefining #stub via attr_accessor * Avoid assignment within condition * Avoid suppressing exception in spec_helper.rb * Auto-correct cops * Autocorrect symbol arrays * Auto-correct string literals * Auto-correct quotes within interpolation * Autocorrect raising exceptions * Avoid using semi-colon as a separator * Set target ruby version * Auto-correct Proc style * Specify 'verbose' style for Hash#has_key? * Allow parallel assignment * Allow 6 digits without underscores * Allow mutable constants This behaviour could've been relied on by users, not worth changing. * Avoid multi-line if modifier usage * Use parentheses for all method definitions * Allow 1.9 hash syntax in specs * Allow one-liners to be wrapped in conditionals * Allow % for formatting * Use each_with_object instead of inject * Allow DateTime in specs * Remove colon method call * Allow bracket symbol arrays * Disable doc checks * Allow all block delimiters in specs * Remove non-ascii quote in comment * Allow is_requesting? as an exception for predicate names * Disable BracesAroundHashParameters check * Move legit items from .rubucop_todo.yml to .rubocop.yml * Disable metrics check for specs --- .rubocop.yml | 86 +++++++++++++++++++++++++++ .rubocop_todo.yml | 40 +++++++++++++ Rakefile | 17 +++++- analytics-ruby.gemspec | 4 ++ lib/segment/analytics.rb | 4 +- lib/segment/analytics/client.rb | 41 +++++++------ lib/segment/analytics/logging.rb | 6 +- lib/segment/analytics/request.rb | 6 +- lib/segment/analytics/response.rb | 1 - lib/segment/analytics/utils.rb | 35 ++++++----- lib/segment/analytics/worker.rb | 2 +- spec/segment/analytics/client_spec.rb | 41 ++++++------- spec/segment/analytics/worker_spec.rb | 12 ++-- spec/spec_helper.rb | 8 +-- 14 files changed, 227 insertions(+), 76 deletions(-) create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..dc74787 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,86 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + # Rubocop doesn't support 1.9, so we'll use the minimum available + TargetRubyVersion: 2.1 + +Layout/IndentHash: + EnforcedStyle: consistent + +Metrics/AbcSize: + Exclude: + - "spec/**/*.rb" + +Metrics/BlockLength: + Exclude: + - "spec/**/*.rb" + +Metrics/ClassLength: + Exclude: + - "spec/**/*.rb" + +Metrics/CyclomaticComplexity: + Exclude: + - "spec/**/*.rb" + +Metrics/LineLength: + Exclude: + - "spec/**/*.rb" + +Metrics/MethodLength: + Exclude: + - "spec/**/*.rb" + +Metrics/PerceivedComplexity: + Exclude: + - "spec/**/*.rb" + +Naming/PredicateName: + NameWhitelist: + - is_requesting? # Can't be renamed, backwards compatibility + +Style/BlockDelimiters: + Exclude: + - 'spec/**/*' + +Style/BracesAroundHashParameters: + Enabled: false + +Style/DateTime: + Exclude: + - 'spec/**/*.rb' + +Style/Documentation: + Enabled: false + +Style/FormatString: + EnforcedStyle: percent + +# Allow one-liner functions to be wrapped in conditionals rather +# than forcing a guard clause +Style/GuardClause: + MinBodyLength: 2 + +Style/HashSyntax: + EnforcedStyle: hash_rockets + Exclude: + - 'spec/**/*.rb' + +Style/ModuleFunction: + Enabled: false + +Style/MutableConstant: + Enabled: false + +Style/NumericLiterals: + MinDigits: 6 + +Style/ParallelAssignment: + Enabled: false + +Style/PreferredHashMethods: + EnforcedStyle: verbose + +# Ruby 1.9 doesn't support percent-styled symbol arrays +Style/SymbolArray: + EnforcedStyle: brackets diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..8057bd2 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,40 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2017-11-23 15:34:00 +0530 using RuboCop version 0.51.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +Lint/RescueException: + Exclude: + - 'lib/segment/analytics/request.rb' + +# Offense count: 9 +Metrics/AbcSize: + Max: 32 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 223 + +# Offense count: 1 +Metrics/CyclomaticComplexity: + Max: 8 + +# Offense count: 36 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 223 + +# Offense count: 9 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 29 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Max: 8 diff --git a/Rakefile b/Rakefile index 8c4fe80..cc4f987 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,22 @@ require 'rspec/core/rake_task' +default_tasks = [] + RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/**/*_spec.rb' end -task :default => :spec +default_tasks << :spec + +# Rubocop doesn't support < 2.1 +if RUBY_VERSION >= "2.1" + require 'rubocop/rake_task' + + RuboCop::RakeTask.new(:rubocop) do |task| + task.patterns = ['lib/**/*.rb','spec/**/*.rb',] + end + + default_tasks << :rubocop +end + +task :default => default_tasks diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index d00fe5a..0fc6467 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -22,4 +22,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '~> 4.1.11' + + if RUBY_VERSION >= "2.1" + spec.add_development_dependency 'rubocop', '~> 0.51.0' + end end diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index da0b400..0105c16 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -9,12 +9,12 @@ module Segment class Analytics - def initialize options = {} + def initialize(options = {}) Request.stub = options[:stub] if options.has_key?(:stub) @client = Segment::Analytics::Client.new options end - def method_missing message, *args, &block + def method_missing(message, *args, &block) if @client.respond_to? message @client.send message, *args, &block else diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 138f819..c0f0d67 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -15,7 +15,7 @@ class Client # :write_key - String of your project's write_key # :max_queue_size - Fixnum of the max calls to remain queued (optional) # :on_error - Proc which handles error calls from the API - def initialize attrs = {} + def initialize(attrs = {}) symbolize_keys! attrs @queue = Queue.new @@ -53,7 +53,7 @@ def flush # :timestamp - Time of when the event occurred. (optional) # :user_id - String of the user id. # :message_id - String of the message id that uniquely identified a message across the API. (optional) - def track attrs + def track(attrs) symbolize_keys! attrs check_user_id! attrs @@ -66,10 +66,10 @@ def track attrs check_timestamp! timestamp if event.nil? || event.empty? - fail ArgumentError, 'Must supply event as a non-empty string' + raise ArgumentError, 'Must supply event as a non-empty string' end - fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash + raise ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash isoify_dates! properties add_context context @@ -99,7 +99,7 @@ def track attrs # :traits - Hash of user traits. (optional) # :user_id - String of the user id # :message_id - String of the message id that uniquely identified a message across the API. (optional) - def identify attrs + def identify(attrs) symbolize_keys! attrs check_user_id! attrs @@ -110,7 +110,7 @@ def identify attrs check_timestamp! timestamp - fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash + raise ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash isoify_dates! traits add_context context @@ -185,7 +185,7 @@ def group(attrs) context = attrs[:context] || {} message_id = attrs[:message_id].to_s if attrs[:message_id] - fail ArgumentError, '.traits must be a hash' unless traits.is_a? Hash + raise ArgumentError, '.traits must be a hash' unless traits.is_a? Hash isoify_dates! traits check_presence! group_id, 'group_id' @@ -228,7 +228,7 @@ def page(attrs) context = attrs[:context] || {} message_id = attrs[:message_id].to_s if attrs[:message_id] - fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties check_timestamp! timestamp @@ -248,6 +248,7 @@ def page(attrs) :type => 'page' }) end + # public: Records a screen view (for a mobile app) # # attrs - Hash @@ -270,7 +271,7 @@ def screen(attrs) context = attrs[:context] || {} message_id = attrs[:message_id].to_s if attrs[:message_id] - fail ArgumentError, '.properties must be a hash' unless properties.is_a? Hash + raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash isoify_dates! properties check_timestamp! timestamp @@ -306,11 +307,15 @@ def queued_messages def enqueue(action) # add our request id for tracing purposes action[:messageId] ||= uid - unless queue_full = @queue.length >= @max_queue_size + + if @queue.length < @max_queue_size ensure_worker_running @queue << action + + true + else + false # Queue is full end - !queue_full end # private: Ensures that a string is non-empty @@ -320,7 +325,7 @@ def enqueue(action) # def check_presence!(obj, name) if obj.nil? || (obj.is_a?(String) && obj.empty?) - fail ArgumentError, "#{name} must be given" + raise ArgumentError, "#{name} must be given" end end @@ -328,20 +333,20 @@ def check_presence!(obj, name) # # context - Hash of call context def add_context(context) - context[:library] = { :name => "analytics-ruby", :version => Segment::Analytics::VERSION.to_s } + context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s } end # private: Checks that the write_key is properly initialized def check_write_key! - fail ArgumentError, 'Write key must be initialized' if @write_key.nil? + raise ArgumentError, 'Write key must be initialized' if @write_key.nil? end # private: Checks the timstamp option to make sure it is a Time. def check_timestamp!(timestamp) - fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time + raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time end - def event attrs + def event(attrs) symbolize_keys! attrs { @@ -354,8 +359,8 @@ def event attrs } end - def check_user_id! attrs - fail ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] + def check_user_id!(attrs) + raise ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] end def ensure_worker_running diff --git a/lib/segment/analytics/logging.rb b/lib/segment/analytics/logging.rb index e7f5229..20b1a7f 100644 --- a/lib/segment/analytics/logging.rb +++ b/lib/segment/analytics/logging.rb @@ -14,12 +14,10 @@ def logger end end - def logger= logger - @logger = logger - end + attr_writer :logger end - def self.included base + def self.included(base) class << base def logger Logging.logger diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 99df829..0a490f1 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -53,10 +53,10 @@ def post(write_key, batch) res = @http.request(request, payload) status = res.code.to_i body = JSON.parse(res.body) - error = body["error"] + error = body['error'] end rescue Exception => e - unless (remaining_retries -=1).zero? + unless (remaining_retries -= 1).zero? sleep(backoff) retry end @@ -71,7 +71,7 @@ def post(write_key, batch) end class << self - attr_accessor :stub + attr_writer :stub def stub @stub || ENV['STUB'] diff --git a/lib/segment/analytics/response.rb b/lib/segment/analytics/response.rb index ebee226..7306ac0 100644 --- a/lib/segment/analytics/response.rb +++ b/lib/segment/analytics/response.rb @@ -13,4 +13,3 @@ def initialize(status = 200, error = nil) end end end - diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index db96405..60bfa52 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -8,7 +8,9 @@ module Utils # public: Return a new hash with keys converted from strings to symbols # def symbolize_keys(hash) - hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo } + hash.each_with_object({}) do |(k, v), memo| + memo[k.to_sym] = v + end end # public: Convert hash keys from strings to symbols in place @@ -20,17 +22,18 @@ def symbolize_keys!(hash) # public: Return a new hash with keys as strings # def stringify_keys(hash) - hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo } + hash.each_with_object({}) do |(k, v), memo| + memo[k.to_s] = v + end end # public: Returns a new hash with all the date values in the into iso8601 # strings # def isoify_dates(hash) - hash.inject({}) { |memo, (k, v)| + hash.each_with_object({}) do |(k, v), memo| memo[k] = datetime_in_iso8601(v) - memo - } + end end # public: Converts all the date values in the into iso8601 strings in place @@ -42,18 +45,18 @@ def isoify_dates!(hash) # public: Returns a uid string # def uid - arr = SecureRandom.random_bytes(16).unpack("NnnnnN") + arr = SecureRandom.random_bytes(16).unpack('NnnnnN') arr[2] = (arr[2] & 0x0fff) | 0x4000 arr[3] = (arr[3] & 0x3fff) | 0x8000 - "%08x-%04x-%04x-%04x-%04x%08x" % arr + '%08x-%04x-%04x-%04x-%04x%08x' % arr end - def datetime_in_iso8601 datetime + def datetime_in_iso8601(datetime) case datetime when Time - time_in_iso8601 datetime + time_in_iso8601 datetime when DateTime - time_in_iso8601 datetime.to_time + time_in_iso8601 datetime.to_time when Date date_in_iso8601 datetime else @@ -61,19 +64,19 @@ def datetime_in_iso8601 datetime end end - def time_in_iso8601 time, fraction_digits = 3 + def time_in_iso8601(time, fraction_digits = 3) fraction = if fraction_digits > 0 - (".%06i" % time.usec)[0, fraction_digits + 1] + ('.%06i' % time.usec)[0, fraction_digits + 1] end - "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(time, true, 'Z')}" + "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}" end - def date_in_iso8601 date - date.strftime("%F") + def date_in_iso8601(date) + date.strftime('%F') end - def formatted_offset time, colon = true, alternate_utc_string = nil + def formatted_offset(time, colon = true, alternate_utc_string = nil) time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon) end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 9288c85..f983d72 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -25,7 +25,7 @@ def initialize(queue, write_key, options = {}) @queue = queue @write_key = write_key @batch_size = options[:batch_size] || Queue::BATCH_SIZE - @on_error = options[:on_error] || Proc.new { |status, error| } + @on_error = options[:on_error] || proc { |status, error| } @batch = [] @lock = Mutex.new end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 89cde66..ebc9a0a 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -43,13 +43,13 @@ class Analytics client.track({ :user_id => 'user', :event => 'Event', - :properties => [1,2,3] + :properties => [1, 2, 3] }) }.to raise_error(ArgumentError) end it 'uses the timestamp given' do - time = Time.parse("1990-07-16 13:30:00.123 UTC") + time = Time.parse('1990-07-16 13:30:00.123 UTC') client.track({ :event => 'testing the timestamp', @@ -83,8 +83,8 @@ class Analytics :properties => { :time => Time.utc(2013), :time_with_zone => Time.zone.parse('2013-01-01'), - :date_time => DateTime.new(2013,1,1), - :date => Date.new(2013,1,1), + :date_time => DateTime.new(2013, 1, 1), + :date => Date.new(2013, 1, 1), :nottime => 'x' } }) @@ -99,7 +99,6 @@ class Analytics end end - describe '#identify' do it 'errors without any user id' do expect { client.identify({}) }.to raise_error(ArgumentError) @@ -125,8 +124,8 @@ class Analytics :traits => { :time => Time.utc(2013), :time_with_zone => Time.zone.parse('2013-01-01'), - :date_time => DateTime.new(2013,1,1), - :date => Date.new(2013,1,1), + :date_time => DateTime.new(2013, 1, 1), + :date => Date.new(2013, 1, 1), :nottime => 'x' } }) @@ -186,8 +185,8 @@ class Analytics :traits => { :time => Time.utc(2013), :time_with_zone => Time.zone.parse('2013-01-01'), - :date_time => DateTime.new(2013,1,1), - :date => Date.new(2013,1,1), + :date_time => DateTime.new(2013, 1, 1), + :date => Date.new(2013, 1, 1), :nottime => 'x' } }) @@ -246,23 +245,25 @@ class Analytics expect(client_with_worker.queued_messages).to eq(0) end - it 'completes when the process forks' do - client_with_worker.identify Queued::IDENTIFY + unless defined? JRUBY_VERSION + it 'completes when the process forks' do + client_with_worker.identify Queued::IDENTIFY - Process.fork do - client_with_worker.track Queued::TRACK - client_with_worker.flush - expect(client_with_worker.queued_messages).to eq(0) - end + Process.fork do + client_with_worker.track Queued::TRACK + client_with_worker.flush + expect(client_with_worker.queued_messages).to eq(0) + end - Process.wait - end unless defined? JRUBY_VERSION + Process.wait + end + end end context 'common' do check_property = proc { |msg, k, v| msg[k] && msg[k] == v } - let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => "coco barked", :name => "coco" } } + let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => 'coco barked', :name => 'coco' } } it 'does not convert ids given as fixnums to strings' do [:track, :screen, :page, :identify].each do |s| @@ -305,7 +306,7 @@ class Analytics it 'sends integrations' do [:track, :screen, :page, :group, :identify, :alias].each do |s| - client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" + client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => 'coco barked', :name => 'coco' message = queue.pop(true) expect(message[:integrations][:All]).to eq(true) expect(message[:integrations][:Salesforce]).to eq(false) diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 1accd22..3abd4d8 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -3,7 +3,7 @@ module Segment class Analytics describe Worker do - describe "#init" do + describe '#init' do it 'accepts string keys' do queue = Queue.new worker = Segment::Analytics::Worker.new(queue, 'secret', 'batch_size' => 100) @@ -36,10 +36,10 @@ class Analytics end it 'executes the error handler, before the request phase ends, if the request is invalid' do - Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, "Some error")) + Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, 'Some error')) status = error = nil - on_error = Proc.new do |yielded_status, yielded_error| + on_error = proc do |yielded_status, yielded_error| sleep 0.2 # Make this take longer than thread spin-up (below) status, error = yielded_status, yielded_error end @@ -48,12 +48,12 @@ class Analytics queue << {} worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error - # This is to ensure that Client#flush doesn’t finish before calling the error handler. + # This is to ensure that Client#flush doesn't finish before calling the error handler. Thread.new { worker.run } sleep 0.1 # First give thread time to spin-up. sleep 0.01 while worker.is_requesting? - Segment::Analytics::Request::any_instance.unstub(:post) + Segment::Analytics::Request.any_instance.unstub(:post) expect(queue).to be_empty expect(status).to eq(400) @@ -61,7 +61,7 @@ class Analytics end it 'does not call on_error if the request is good' do - on_error = Proc.new do |status, error| + on_error = proc do |status, error| puts "#{status}, #{error}" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e67ca5d..33cf320 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,7 +18,7 @@ class Analytics } } - IDENTIFY = { + IDENTIFY = { :traits => { :likes_animals => true, :instrument => 'Guitar', @@ -97,11 +97,11 @@ def eventually(options = {}) loop do begin yield + return rescue RSpec::Expectations::ExpectationNotMetError => error + raise error if Time.now >= time_limit + sleep interval end - return if error.nil? - raise error if Time.now >= time_limit - sleep interval end end end From 940345fd08e90a442b99084f4c71f402eff47d43 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 29 Nov 2017 22:52:07 +0530 Subject: [PATCH 165/322] Upload coverage stats to codecov.io --- analytics-ruby.gemspec | 2 ++ spec/spec_helper.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 0fc6467..ff9636f 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -26,4 +26,6 @@ Gem::Specification.new do |spec| if RUBY_VERSION >= "2.1" spec.add_development_dependency 'rubocop', '~> 0.51.0' end + + spec.add_development_dependency 'codecov', '~> 0.1.4' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 33cf320..8d8bdb8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,9 @@ +# https://github.com/codecov/codecov-ruby#usage +require 'simplecov' +SimpleCov.start +require 'codecov' +SimpleCov.formatter = SimpleCov::Formatter::Codecov + require 'segment/analytics' require 'active_support/time' From 1dd39ef885e0a40f804c35b47070bc431008528d Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 1 Dec 2017 12:31:26 +0530 Subject: [PATCH 166/322] Handle HTTP status code failures (#132) * Separate request construction logic from retries * Trim lines in request_spec.rb to < 80 chars * Add function to retry a block without exceptions raised * Retry requests on HTTP status code errors --- lib/segment/analytics/request.rb | 104 +++++++++++++++++------- lib/segment/analytics/worker.rb | 2 +- spec/segment/analytics/request_spec.rb | 106 ++++++++++++++++++------- 3 files changed, 154 insertions(+), 58 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 0a490f1..e2d8073 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -36,38 +36,86 @@ def initialize(options = {}) # # returns - Response of the status and error if it exists def post(write_key, batch) - status, error = nil, nil - remaining_retries = @retries - backoff = @backoff - headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } + last_response, exception = retry_with_backoff(@retries, @backoff) do + status_code, body = send_request(write_key, batch) + error = JSON.parse(body)['error'] + should_retry = should_retry_request?(status_code, body) + + [Response.new(status_code, error), should_retry] + end + + if exception + logger.error(exception.message) + exception.backtrace.each { |line| logger.error(line) } + Response.new(-1, "Connection error: #{exception}") + else + last_response + end + end + + private + + def should_retry_request?(status_code, body) + if status_code >= 500 + true # Server error + elsif status_code == 429 + true # Rate limited + elsif status_code >= 400 + logger.error(body) + false # Client error. Do not retry, but log + else + false + end + end + + # Takes a block that returns [result, should_retry]. + # + # Retries upto `retries_remaining` times, if `should_retry` is false or + # an exception is raised. + # + # Returns [last_result, raised_exception] + def retry_with_backoff(retries_remaining, backoff, &block) + result, caught_exception = nil + should_retry = false + begin - payload = JSON.generate :sentAt => datetime_in_iso8601(Time.new), :batch => batch - request = Net::HTTP::Post.new(@path, headers) - request.basic_auth write_key, nil - - if self.class.stub - status = 200 - error = nil - logger.debug "stubbed request to #{@path}: write key = #{write_key}, payload = #{payload}" - else - res = @http.request(request, payload) - status = res.code.to_i - body = JSON.parse(res.body) - error = body['error'] - end + result, should_retry = yield + return [result, nil] unless should_retry rescue Exception => e - unless (remaining_retries -= 1).zero? - sleep(backoff) - retry - end - - logger.error e.message - e.backtrace.each { |line| logger.error line } - status = -1 - error = "Connection error: #{e}" + should_retry = true + caught_exception = e end - Response.new status, error + if should_retry && (retries_remaining > 1) + sleep(backoff) + retry_with_backoff(retries_remaining - 1, backoff, &block) + else + [result, caught_exception] + end + end + + # Sends a request for the batch, returns [status_code, body] + def send_request(write_key, batch) + headers = { + 'Content-Type' => 'application/json', + 'accept' => 'application/json' + } + payload = JSON.generate( + :sentAt => datetime_in_iso8601(Time.now), + :batch => batch + ) + request = Net::HTTP::Post.new(@path, headers) + request.basic_auth(write_key, nil) + + if self.class.stub + logger.debug "stubbed request to #{@path}: " \ + "write key = #{write_key}, batch = JSON.generate(#{batch})" + + [200, '{}'] + else + response = @http.request(request, payload) + [response.code.to_i, response.body] + end end class << self diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f983d72..f619a1b 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -44,7 +44,7 @@ def run res = Request.new.post @write_key, @batch - @on_error.call res.status, res.error unless res.status == 200 + @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } end diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb index 40bdc3d..d556eb9 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/request_spec.rb @@ -37,19 +37,25 @@ class Analytics context 'no options are set' do it 'sets a default path' do - expect(subject.instance_variable_get(:@path)).to eq(described_class::PATH) + path = subject.instance_variable_get(:@path) + expect(path).to eq(described_class::PATH) end it 'sets a default retries' do - expect(subject.instance_variable_get(:@retries)).to eq(described_class::RETRIES) + retries = subject.instance_variable_get(:@retries) + expect(retries).to eq(described_class::RETRIES) end it 'sets a default backoff' do - expect(subject.instance_variable_get(:@backoff)).to eq(described_class::BACKOFF) + backoff = subject.instance_variable_get(:@backoff) + expect(backoff).to eq(described_class::BACKOFF) end it 'initializes a new Net::HTTP with default host and port' do - expect(Net::HTTP).to receive(:new).with(described_class::HOST, described_class::PORT) + expect(Net::HTTP).to receive(:new).with( + described_class::HOST, + described_class::PORT + ) described_class.new end end @@ -92,7 +98,9 @@ class Analytics end describe '#post' do - let(:response) { Net::HTTPResponse.new(http_version, status_code, response_body) } + let(:response) { + Net::HTTPResponse.new(http_version, status_code, response_body) + } let(:http_version) { 1.1 } let(:status_code) { 200 } let(:response_body) { {}.to_json } @@ -100,19 +108,28 @@ class Analytics let(:batch) { [] } before do - allow(subject.instance_variable_get(:@http)).to receive(:request) { response } + http = subject.instance_variable_get(:@http) + allow(http).to receive(:request) { response } allow(response).to receive(:body) { response_body } end it 'initalizes a new Net::HTTP::Post with path and default headers' do path = subject.instance_variable_get(:@path) - default_headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } - expect(Net::HTTP::Post).to receive(:new).with(path, default_headers).and_call_original + default_headers = { + 'Content-Type' => 'application/json', + 'accept' => 'application/json' + } + expect(Net::HTTP::Post).to receive(:new).with( + path, default_headers + ).and_call_original + subject.post(write_key, batch) end it 'adds basic auth to the Net::HTTP::Post' do - expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth).with(write_key, nil) + expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth) + .with(write_key, nil) + subject.post(write_key, batch) end @@ -136,6 +153,38 @@ class Analytics end context 'a real request' do + RSpec.shared_examples('retried request') do |status_code, body| + let(:status_code) { status_code } + let(:body) { body } + let(:retries) { 4 } + let(:backoff) { 1 } + subject { described_class.new(retries: retries, backoff: backoff) } + + it 'retries the request' do + expect(subject) + .to receive(:sleep) + .exactly(retries - 1).times + .with(backoff) + .and_return(nil) + subject.post(write_key, batch) + end + end + + RSpec.shared_examples('non-retried request') do |status_code, body| + let(:status_code) { status_code } + let(:body) { body } + let(:retries) { 4 } + let(:backoff) { 1 } + subject { described_class.new(retries: retries, backoff: backoff) } + + it 'does not retry the request' do + expect(subject) + .to receive(:sleep) + .never + subject.post(write_key, batch) + end + end + context 'request is successful' do let(:status_code) { 201 } it 'returns a response code' do @@ -156,33 +205,32 @@ class Analytics end end - context 'request or parsing of response results in an exception' do - let(:response_body) { 'Malformed JSON ---' } + context 'a request returns a failure status code' do + # Server errors must be retried + it_behaves_like('retried request', 500, '{}') + it_behaves_like('retried request', 503, '{}') - let(:backoff) { 0 } + # All 4xx errors other than 429 (rate limited) must be retried + it_behaves_like('retried request', 429, '{}') + it_behaves_like('non-retried request', 404, '{}') + it_behaves_like('non-retried request', 400, '{}') + end - subject { described_class.new(retries: retries, backoff: backoff) } + context 'request or parsing of response results in an exception' do + let(:response_body) { 'Malformed JSON ---' } - context 'remaining retries is > 1' do - let(:retries) { 2 } + subject { described_class.new(backoff: 0) } - it 'sleeps' do - expect(subject).to receive(:sleep).exactly(retries - 1).times - subject.post(write_key, batch) - end + it 'returns a -1 for status' do + expect(subject.post(write_key, batch).status).to eq(-1) end - context 'remaining retries is 1' do - let(:retries) { 1 } - - it 'returns a -1 for status' do - expect(subject.post(write_key, batch).status).to eq(-1) - end - - it 'has a connection error' do - expect(subject.post(write_key, batch).error).to match(/Connection error/) - end + it 'has a connection error' do + error = subject.post(write_key, batch).error + expect(error).to match(/Connection error/) end + + it_behaves_like('retried request', 200, 'Malformed JSON ---') end end end From d75a19c2c68ac54381c094353ce7dc7b922a5b2d Mon Sep 17 00:00:00 2001 From: Gopal Patel Date: Sun, 28 Feb 2016 21:54:20 -0800 Subject: [PATCH 167/322] Add analytics-ruby.rb for easier Gemfile use --- README.md | 4 ++-- lib/analytics-ruby.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 lib/analytics-ruby.rb diff --git a/README.md b/README.md index c7c07fd..e611131 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ analytics-ruby is a ruby client for [Segment](https://segment.com) Into Gemfile from rubygems.org: ```ruby -gem 'analytics-ruby', require: "segment" +gem 'analytics-ruby' ``` Into environment gems from rubygems.org: -```ruby +``` gem install 'analytics-ruby' ``` diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb new file mode 100644 index 0000000..801ad01 --- /dev/null +++ b/lib/analytics-ruby.rb @@ -0,0 +1 @@ +require 'segment' From 9c3104781a6b6566b4fa32566243e95d04cd13c8 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 4 Dec 2017 12:40:20 +0530 Subject: [PATCH 168/322] Add file naming exception to rubocop.yml --- .rubocop.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index dc74787..d9751fd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,6 +35,10 @@ Metrics/PerceivedComplexity: Exclude: - "spec/**/*.rb" +Naming/FileName: + Exclude: + - lib/analytics-ruby.rb # Gem name, added for easier Gemfile usage + Naming/PredicateName: NameWhitelist: - is_requesting? # Can't be renamed, backwards compatibility From 954a2c91dd8b893a786fdf55f5db500846d14f7c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 4 Dec 2017 12:33:50 +0530 Subject: [PATCH 169/322] Use HEADERS from defaults.rb --- lib/segment/analytics/defaults.rb | 3 ++- lib/segment/analytics/request.rb | 8 ++------ spec/segment/analytics/request_spec.rb | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index aeb933e..b675e90 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -6,7 +6,8 @@ module Request PORT = 443 PATH = '/v1/import' SSL = true - HEADERS = { :accept => 'application/json' } + HEADERS = { 'Accept' => 'application/json', + 'Content-Type' => 'application/json' } RETRIES = 4 BACKOFF = 30.0 end diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index e2d8073..74df4d0 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -19,7 +19,7 @@ def initialize(options = {}) options[:host] ||= HOST options[:port] ||= PORT options[:ssl] ||= SSL - options[:headers] ||= HEADERS + @headers = options[:headers] || HEADERS @path = options[:path] || PATH @retries = options[:retries] || RETRIES @backoff = options[:backoff] || BACKOFF @@ -96,15 +96,11 @@ def retry_with_backoff(retries_remaining, backoff, &block) # Sends a request for the batch, returns [status_code, body] def send_request(write_key, batch) - headers = { - 'Content-Type' => 'application/json', - 'accept' => 'application/json' - } payload = JSON.generate( :sentAt => datetime_in_iso8601(Time.now), :batch => batch ) - request = Net::HTTP::Post.new(@path, headers) + request = Net::HTTP::Post.new(@path, @headers) request.basic_auth(write_key, nil) if self.class.stub diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb index d556eb9..1695b5f 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/request_spec.rb @@ -117,7 +117,7 @@ class Analytics path = subject.instance_variable_get(:@path) default_headers = { 'Content-Type' => 'application/json', - 'accept' => 'application/json' + 'Accept' => 'application/json' } expect(Net::HTTP::Post).to receive(:new).with( path, default_headers From bd084e6af945c47c0047a5626dde232d684b18ba Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 4 Dec 2017 14:44:15 +0530 Subject: [PATCH 170/322] Add exponential backoff --- lib/segment/analytics/backoff_policy.rb | 49 ++++++++++ lib/segment/analytics/defaults.rb | 8 +- lib/segment/analytics/request.rb | 15 +-- spec/segment/analytics/backoff_policy_spec.rb | 92 +++++++++++++++++++ spec/segment/analytics/request_spec.rb | 26 +++--- spec/spec_helper.rb | 12 +++ 6 files changed, 184 insertions(+), 18 deletions(-) create mode 100644 lib/segment/analytics/backoff_policy.rb create mode 100644 spec/segment/analytics/backoff_policy_spec.rb diff --git a/lib/segment/analytics/backoff_policy.rb b/lib/segment/analytics/backoff_policy.rb new file mode 100644 index 0000000..394a2c1 --- /dev/null +++ b/lib/segment/analytics/backoff_policy.rb @@ -0,0 +1,49 @@ +require 'segment/analytics/defaults' + +module Segment + class Analytics + class BackoffPolicy + include Segment::Analytics::Defaults::BackoffPolicy + + # @param [Hash] opts + # @option opts [Numeric] :min_timeout_ms The minimum backoff timeout + # @option opts [Numeric] :max_timeout_ms The maximum backoff timeout + # @option opts [Numeric] :multiplier The value to multiply the current + # interval with for each retry attempt + # @option opts [Numeric] :randomization_factor The randomization factor + # to use to create a range around the retry interval + def initialize(opts = {}) + @min_timeout_ms = opts[:min_timeout_ms] || MIN_TIMEOUT_MS + @max_timeout_ms = opts[:max_timeout_ms] || MAX_TIMEOUT_MS + @multiplier = opts[:multiplier] || MULTIPLIER + @randomization_factor = opts[:randomization_factor] || RANDOMIZATION_FACTOR + + @attempts = 0 + end + + # @return [Numeric] the next backoff interval, in milliseconds. + def next_interval + interval = @min_timeout_ms * (@multiplier**@attempts) + interval = add_jitter(interval, @randomization_factor) + + @attempts += 1 + + [interval, @max_timeout_ms].min + end + + private + + def add_jitter(base, randomization_factor) + random_number = rand + max_deviation = base * randomization_factor + deviation = random_number * max_deviation + + if random_number < 0.5 + base - deviation + else + base + deviation + end + end + end + end +end diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index b675e90..f5a7b18 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -9,13 +9,19 @@ module Request HEADERS = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } RETRIES = 4 - BACKOFF = 30.0 end module Queue BATCH_SIZE = 100 MAX_SIZE = 10000 end + + module BackoffPolicy + MIN_TIMEOUT_MS = 100 + MAX_TIMEOUT_MS = 10000 + MULTIPLIER = 1.5 + RANDOMIZATION_FACTOR = 0.5 + end end end end diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 74df4d0..026950a 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -2,6 +2,7 @@ require 'segment/analytics/utils' require 'segment/analytics/response' require 'segment/analytics/logging' +require 'segment/analytics/backoff_policy' require 'net/http' require 'net/https' require 'json' @@ -22,7 +23,8 @@ def initialize(options = {}) @headers = options[:headers] || HEADERS @path = options[:path] || PATH @retries = options[:retries] || RETRIES - @backoff = options[:backoff] || BACKOFF + @backoff_policy = + options[:backoff_policy] || Segment::Analytics::BackoffPolicy.new http = Net::HTTP.new(options[:host], options[:port]) http.use_ssl = options[:ssl] @@ -36,7 +38,7 @@ def initialize(options = {}) # # returns - Response of the status and error if it exists def post(write_key, batch) - last_response, exception = retry_with_backoff(@retries, @backoff) do + last_response, exception = retry_with_backoff(@retries) do status_code, body = send_request(write_key, batch) error = JSON.parse(body)['error'] should_retry = should_retry_request?(status_code, body) @@ -71,10 +73,11 @@ def should_retry_request?(status_code, body) # Takes a block that returns [result, should_retry]. # # Retries upto `retries_remaining` times, if `should_retry` is false or - # an exception is raised. + # an exception is raised. `@backoff_policy` is used to determine the + # duration to sleep between attempts # # Returns [last_result, raised_exception] - def retry_with_backoff(retries_remaining, backoff, &block) + def retry_with_backoff(retries_remaining, &block) result, caught_exception = nil should_retry = false @@ -87,8 +90,8 @@ def retry_with_backoff(retries_remaining, backoff, &block) end if should_retry && (retries_remaining > 1) - sleep(backoff) - retry_with_backoff(retries_remaining - 1, backoff, &block) + sleep(@backoff_policy.next_interval.to_f / 1000) + retry_with_backoff(retries_remaining - 1, &block) else [result, caught_exception] end diff --git a/spec/segment/analytics/backoff_policy_spec.rb b/spec/segment/analytics/backoff_policy_spec.rb new file mode 100644 index 0000000..75dc9fc --- /dev/null +++ b/spec/segment/analytics/backoff_policy_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +module Segment + class Analytics + describe BackoffPolicy do + describe '#initialize' do + context 'no options are given' do + it 'sets default min_timeout_ms' do + actual = subject.instance_variable_get(:@min_timeout_ms) + expect(actual).to eq(described_class::MIN_TIMEOUT_MS) + end + + it 'sets default max_timeout_ms' do + actual = subject.instance_variable_get(:@max_timeout_ms) + expect(actual).to eq(described_class::MAX_TIMEOUT_MS) + end + + it 'sets default multiplier' do + actual = subject.instance_variable_get(:@multiplier) + expect(actual).to eq(described_class::MULTIPLIER) + end + + it 'sets default randomization factor' do + actual = subject.instance_variable_get(:@randomization_factor) + expect(actual).to eq(described_class::RANDOMIZATION_FACTOR) + end + end + + context 'options are given' do + let(:min_timeout_ms) { 1234 } + let(:max_timeout_ms) { 5678 } + let(:multiplier) { 24 } + let(:randomization_factor) { 0.4 } + + let(:options) do + { + min_timeout_ms: min_timeout_ms, + max_timeout_ms: max_timeout_ms, + multiplier: multiplier, + randomization_factor: randomization_factor + } + end + + subject { described_class.new(options) } + + it 'sets passed in min_timeout_ms' do + actual = subject.instance_variable_get(:@min_timeout_ms) + expect(actual).to eq(min_timeout_ms) + end + + it 'sets passed in max_timeout_ms' do + actual = subject.instance_variable_get(:@max_timeout_ms) + expect(actual).to eq(max_timeout_ms) + end + + it 'sets passed in multiplier' do + actual = subject.instance_variable_get(:@multiplier) + expect(actual).to eq(multiplier) + end + + it 'sets passed in randomization_factor' do + actual = subject.instance_variable_get(:@randomization_factor) + expect(actual).to eq(randomization_factor) + end + end + end + + describe '#next_interval' do + subject { + described_class.new( + min_timeout_ms: 1000, + max_timeout_ms: 10000, + multiplier: 2, + randomization_factor: 0.5 + ) + } + + it 'returns exponentially increasing durations' do + expect(subject.next_interval).to be_within(500).of(1000) + expect(subject.next_interval).to be_within(1000).of(2000) + expect(subject.next_interval).to be_within(2000).of(4000) + expect(subject.next_interval).to be_within(4000).of(8000) + end + + it 'caps maximum duration at max_timeout_secs' do + 10.times { subject.next_interval } + expect(subject.next_interval).to eq(10000) + end + end + end + end +end diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb index 1695b5f..babc928 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/request_spec.rb @@ -46,9 +46,9 @@ class Analytics expect(retries).to eq(described_class::RETRIES) end - it 'sets a default backoff' do - backoff = subject.instance_variable_get(:@backoff) - expect(backoff).to eq(described_class::BACKOFF) + it 'sets a default backoff policy' do + backoff_policy = subject.instance_variable_get(:@backoff_policy) + expect(backoff_policy).to be_a(Segment::Analytics::BackoffPolicy) end it 'initializes a new Net::HTTP with default host and port' do @@ -63,14 +63,14 @@ class Analytics context 'options are given' do let(:path) { 'my/cool/path' } let(:retries) { 1234 } - let(:backoff) { 10 } + let(:backoff_policy) { FakeBackoffPolicy.new([1, 2, 3]) } let(:host) { 'http://www.example.com' } let(:port) { 8080 } let(:options) do { path: path, retries: retries, - backoff: backoff, + backoff_policy: backoff_policy, host: host, port: port } @@ -86,8 +86,9 @@ class Analytics expect(subject.instance_variable_get(:@retries)).to eq(retries) end - it 'sets passed in backoff' do - expect(subject.instance_variable_get(:@backoff)).to eq(backoff) + it 'sets passed in backoff backoff policy' do + expect(subject.instance_variable_get(:@backoff_policy)) + .to eq(backoff_policy) end it 'initializes a new Net::HTTP with passed in host and port' do @@ -157,14 +158,17 @@ class Analytics let(:status_code) { status_code } let(:body) { body } let(:retries) { 4 } - let(:backoff) { 1 } - subject { described_class.new(retries: retries, backoff: backoff) } + let(:backoff_policy) { FakeBackoffPolicy.new([1000, 1000, 1000]) } + subject { + described_class.new(retries: retries, + backoff_policy: backoff_policy) + } it 'retries the request' do expect(subject) .to receive(:sleep) .exactly(retries - 1).times - .with(backoff) + .with(1) .and_return(nil) subject.post(write_key, batch) end @@ -219,7 +223,7 @@ class Analytics context 'request or parsing of response results in an exception' do let(:response_body) { 'Malformed JSON ---' } - subject { described_class.new(backoff: 0) } + subject { described_class.new(retries: 0) } it 'returns a -1 for status' do expect(subject.post(write_key, batch).status).to eq(-1) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8d8bdb8..8bc41b0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -90,6 +90,18 @@ def run end end +# A backoff policy that returns a fixed list of values +class FakeBackoffPolicy + def initialize(interval_values) + @interval_values = interval_values + end + + def next_interval + raise 'FakeBackoffPolicy has no values left' if @interval_values.empty? + @interval_values.shift + end +end + # usage: # it "should return a result of 5" do # eventually(options: {timeout: 1}) { long_running_thing.result.should eq(5) } From 0c6ccddc20ed5646552da46bb6846d79dc356d03 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 5 Dec 2017 13:09:29 +0530 Subject: [PATCH 171/322] Tweak retry count to match segmentio/analytics-go --- lib/segment/analytics/defaults.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index f5a7b18..d641420 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -8,7 +8,7 @@ module Request SSL = true HEADERS = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } - RETRIES = 4 + RETRIES = 10 end module Queue From 58a955eb216264131e00a2f531d74182fb2c6c41 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 7 Dec 2017 14:22:28 +0530 Subject: [PATCH 172/322] Add User-Agent header --- lib/segment/analytics/defaults.rb | 3 ++- spec/segment/analytics/request_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index d641420..550bf67 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -7,7 +7,8 @@ module Request PATH = '/v1/import' SSL = true HEADERS = { 'Accept' => 'application/json', - 'Content-Type' => 'application/json' } + 'Content-Type' => 'application/json', + 'User-Agent' => "analytics-ruby/#{Analytics::VERSION}" } RETRIES = 10 end diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb index babc928..721d7e9 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/request_spec.rb @@ -118,7 +118,8 @@ class Analytics path = subject.instance_variable_get(:@path) default_headers = { 'Content-Type' => 'application/json', - 'Accept' => 'application/json' + 'Accept' => 'application/json', + 'User-Agent' => "analytics-ruby/#{Analytics::VERSION}" } expect(Net::HTTP::Post).to receive(:new).with( path, default_headers From aa35213c1dd082c462c7cbadbfa55f5f1c0e0626 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 15 Dec 2017 21:23:36 +0530 Subject: [PATCH 173/322] Add an end-to-end test (#139) * Add an end-to-end test * Enable E2E tests in .travis.yml * Replace httparty with faraday for ruby 1.9 compat * Ignore spec folder for code coverage * Don't set E2E env var, as it is set in Travis --- Rakefile | 1 + analytics-ruby.gemspec | 2 ++ codecov.yml | 2 ++ spec/helpers/runscope_client.rb | 38 +++++++++++++++++++++++ spec/segment/analytics/e2e_spec.rb | 48 ++++++++++++++++++++++++++++++ spec/spec_helper.rb | 1 + 6 files changed, 92 insertions(+) create mode 100644 codecov.yml create mode 100644 spec/helpers/runscope_client.rb create mode 100644 spec/segment/analytics/e2e_spec.rb diff --git a/Rakefile b/Rakefile index cc4f987..626fcbe 100644 --- a/Rakefile +++ b/Rakefile @@ -4,6 +4,7 @@ default_tasks = [] RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/**/*_spec.rb' + spec.rspec_opts = "--tag ~e2e" if ENV["RUN_E2E_TESTS"] != "true" end default_tasks << :spec diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index ff9636f..1670cf2 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -22,6 +22,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '~> 4.1.11' + spec.add_development_dependency 'faraday', '~> 0.13' + spec.add_development_dependency 'pmap', '~> 1.1' if RUBY_VERSION >= "2.1" spec.add_development_dependency 'rubocop', '~> 0.51.0' diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..c6e5dff --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "spec/**/*" diff --git a/spec/helpers/runscope_client.rb b/spec/helpers/runscope_client.rb new file mode 100644 index 0000000..7d0e7f8 --- /dev/null +++ b/spec/helpers/runscope_client.rb @@ -0,0 +1,38 @@ +require 'faraday' +require 'pmap' + +class RunscopeClient + def initialize(api_token) + headers = { 'Authorization' => "Bearer #{api_token}" } + @conn = Faraday.new('https://api.runscope.com', headers: headers) + end + + def requests(bucket_key) + with_retries(3) do + response = @conn.get("/buckets/#{bucket_key}/messages", count: 10) + + raise "Runscope error. #{response.body}" unless response.status == 200 + + message_uuids = JSON.parse(response.body)['data'].map { |message| + message.fetch('uuid') + } + + message_uuids.pmap { |uuid| + response = @conn.get("/buckets/#{bucket_key}/messages/#{uuid}") + raise "Runscope error. #{response.body}" unless response.status == 200 + JSON.parse(response.body).fetch('data').fetch('request') + } + end + end + + private + + def with_retries(max_retries) + retries ||= 0 + yield + rescue StandardError => e + retries += 1 + retry if retries < max_retries + raise e + end +end diff --git a/spec/segment/analytics/e2e_spec.rb b/spec/segment/analytics/e2e_spec.rb new file mode 100644 index 0000000..e528d59 --- /dev/null +++ b/spec/segment/analytics/e2e_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +module Segment + # End-to-end tests that send events to a segment source and verifies that a + # webhook connected to the source (configured manually via the app) is able + # to receive the data sent by this library. + describe 'End-to-end tests', e2e: true do + # Segment write key for + # https://app.segment.com/segment-libraries/sources/analytics_ruby_e2e_test/overview. + # + # This source is configured to send events to the Runscope bucket used by + # this test. + WRITE_KEY = 'qhdMksLsQTi9MES3CHyzsWRRt4ub5VM6' + + # Runscope bucket key for https://www.runscope.com/stream/umkvkgv7ndby + RUNSCOPE_BUCKET_KEY = 'umkvkgv7ndby' + + let(:client) { Segment::Analytics.new(write_key: WRITE_KEY) } + let(:runscope_client) { RunscopeClient.new(ENV.fetch('RUNSCOPE_TOKEN')) } + + it 'tracks events' do + id = SecureRandom.uuid + client.track( + user_id: 'dummy_user_id', + event: 'E2E Test', + properties: { id: id } + ) + client.flush + + # Allow events to propagate to runscope + eventually(timeout: 30) { + expect(has_matching_request?(id)).to eq(true) + } + end + + def has_matching_request?(id) + captured_requests = runscope_client.requests(RUNSCOPE_BUCKET_KEY) + captured_requests.any? do |request| + begin + body = JSON.parse(request['body']) + body['properties'] && body['properties']['id'] == id + rescue JSON::ParserError + false + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8bc41b0..7cd6c2f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,6 +6,7 @@ require 'segment/analytics' require 'active_support/time' +require './spec/helpers/runscope_client' # Setting timezone for ActiveSupport::TimeWithZone to UTC Time.zone = 'UTC' From c429653cd16e59bda15bc79742be9c26e0f954de Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 16 Dec 2017 03:41:28 +0530 Subject: [PATCH 174/322] Spawn worker after adding item to queue (#138) --- lib/segment/analytics/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index c0f0d67..b216c7a 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -309,8 +309,8 @@ def enqueue(action) action[:messageId] ||= uid if @queue.length < @max_queue_size - ensure_worker_running @queue << action + ensure_worker_running true else From 779cc774c976814b5ae4a0e08687910871215446 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 20 Dec 2017 22:48:02 +0530 Subject: [PATCH 175/322] Avoid catching Exception, only catch StandardError instead (#141) --- lib/segment/analytics/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 026950a..d077a3a 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -84,7 +84,7 @@ def retry_with_backoff(retries_remaining, &block) begin result, should_retry = yield return [result, nil] unless should_retry - rescue Exception => e + rescue StandardError => e should_retry = true caught_exception = e end From 114af0b4ac8784eb781ba0cf295a4ae7a5bfadef Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 5 Jan 2018 17:46:19 +0530 Subject: [PATCH 176/322] Use a custom 'Message' class --- lib/segment/analytics/message.rb | 17 +++++++++++++++++ lib/segment/analytics/worker.rb | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 lib/segment/analytics/message.rb diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb new file mode 100644 index 0000000..47b47b0 --- /dev/null +++ b/lib/segment/analytics/message.rb @@ -0,0 +1,17 @@ +require 'forwardable' + +module Segment + class Analytics + # Represents a message to be sent to the API + class Message + extend Forwardable + + def initialize(hash) + @hash = hash + end + + def_delegators :@hash, :to_json # TODO: Cache and reuse + def_delegators :@hash, :[] + end + end +end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f619a1b..5e94b5f 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,7 +1,7 @@ require 'segment/analytics/defaults' -require 'segment/analytics/utils' -require 'segment/analytics/defaults' +require 'segment/analytics/message' require 'segment/analytics/request' +require 'segment/analytics/utils' module Segment class Analytics @@ -38,7 +38,7 @@ def run @lock.synchronize do until @batch.length >= @batch_size || @queue.empty? - @batch << @queue.pop + @batch << Message.new(@queue.pop) end end From 5174fbdec01fdaf639a59ff1dc0faccee94abee9 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 5 Jan 2018 18:03:22 +0530 Subject: [PATCH 177/322] Add custom 'MessageBatch' class --- lib/segment/analytics/message_batch.rb | 18 ++++++++++++++++++ lib/segment/analytics/worker.rb | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 lib/segment/analytics/message_batch.rb diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb new file mode 100644 index 0000000..bc9c95c --- /dev/null +++ b/lib/segment/analytics/message_batch.rb @@ -0,0 +1,18 @@ +module Segment + class Analytics + # A batch of `Message`s to be sent to the API + class MessageBatch + extend Forwardable + + def initialize + @messages = [] + end + + def_delegators :@messages, :to_json # TODO: Cache and reuse + def_delegators :@messages, :<< + def_delegators :@messages, :clear + def_delegators :@messages, :empty? + def_delegators :@messages, :length + end + end +end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 5e94b5f..93e2067 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,5 +1,6 @@ require 'segment/analytics/defaults' require 'segment/analytics/message' +require 'segment/analytics/message_batch' require 'segment/analytics/request' require 'segment/analytics/utils' @@ -26,7 +27,7 @@ def initialize(queue, write_key, options = {}) @write_key = write_key @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || proc { |status, error| } - @batch = [] + @batch = MessageBatch.new @lock = Mutex.new end From f6e659b44be55a27d2d7ec94a8f7a61d226be425 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 5 Jan 2018 18:04:14 +0530 Subject: [PATCH 178/322] Reject messages that exceed the maximum allowed size --- lib/segment/analytics/defaults.rb | 4 ++++ lib/segment/analytics/message.rb | 6 +++++ lib/segment/analytics/message_batch.rb | 12 +++++++++- spec/segment/analytics/message_batch_spec.rb | 25 ++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 spec/segment/analytics/message_batch_spec.rb diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index 550bf67..18cf861 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -17,6 +17,10 @@ module Queue MAX_SIZE = 10000 end + module Message + MAX_BYTES = 32768 # 32Kb + end + module BackoffPolicy MIN_TIMEOUT_MS = 100 MAX_TIMEOUT_MS = 10000 diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb index 47b47b0..8b62f82 100644 --- a/lib/segment/analytics/message.rb +++ b/lib/segment/analytics/message.rb @@ -1,5 +1,7 @@ require 'forwardable' +require 'segment/analytics/defaults' + module Segment class Analytics # Represents a message to be sent to the API @@ -10,6 +12,10 @@ def initialize(hash) @hash = hash end + def too_big? + to_json.bytesize > Defaults::Message::MAX_BYTES + end + def_delegators :@hash, :to_json # TODO: Cache and reuse def_delegators :@hash, :[] end diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index bc9c95c..6bf77bf 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -1,15 +1,25 @@ +require 'segment/analytics/logging' + module Segment class Analytics # A batch of `Message`s to be sent to the API class MessageBatch extend Forwardable + include Segment::Analytics::Logging def initialize @messages = [] end + def <<(message) + if message.too_big? + logger.error('a message exceeded the maximum allowed size') + else + @messages << message + end + end + def_delegators :@messages, :to_json # TODO: Cache and reuse - def_delegators :@messages, :<< def_delegators :@messages, :clear def_delegators :@messages, :empty? def_delegators :@messages, :length diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb new file mode 100644 index 0000000..e50f4d0 --- /dev/null +++ b/spec/segment/analytics/message_batch_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +module Segment + class Analytics + describe MessageBatch do + describe '#<<' do + subject { described_class.new } + + it 'appends messages' do + subject << Message.new('a' => 'b') + expect(subject.length).to eq(1) + end + + it 'rejects messages that exceed the maximum allowed size' do + max_bytes = Segment::Analytics::Defaults::Message::MAX_BYTES + hash = { 'a' => 'b' * max_bytes } + message = Message.new(hash) + + subject << message + expect(subject.length).to eq(0) + end + end + end + end +end From feedf1a345f8f5c6e5386401fba858b3af32a7a5 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 5 Jan 2018 18:24:38 +0530 Subject: [PATCH 179/322] Cache JSON generation for individual messages --- lib/segment/analytics/message.rb | 11 ++++---- lib/segment/analytics/message_batch.rb | 2 +- spec/segment/analytics/message_spec.rb | 35 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 spec/segment/analytics/message_spec.rb diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb index 8b62f82..b11b121 100644 --- a/lib/segment/analytics/message.rb +++ b/lib/segment/analytics/message.rb @@ -1,13 +1,9 @@ -require 'forwardable' - require 'segment/analytics/defaults' module Segment class Analytics # Represents a message to be sent to the API class Message - extend Forwardable - def initialize(hash) @hash = hash end @@ -16,8 +12,11 @@ def too_big? to_json.bytesize > Defaults::Message::MAX_BYTES end - def_delegators :@hash, :to_json # TODO: Cache and reuse - def_delegators :@hash, :[] + # Since the hash is expected to not be modified (set at initialization), + # the JSON version can be cached after the first computation. + def to_json(*args) + @json ||= @hash.to_json(*args) + end end end end diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 6bf77bf..0cf1dc4 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -19,7 +19,7 @@ def <<(message) end end - def_delegators :@messages, :to_json # TODO: Cache and reuse + def_delegators :@messages, :to_json def_delegators :@messages, :clear def_delegators :@messages, :empty? def_delegators :@messages, :length diff --git a/spec/segment/analytics/message_spec.rb b/spec/segment/analytics/message_spec.rb new file mode 100644 index 0000000..b156183 --- /dev/null +++ b/spec/segment/analytics/message_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +module Segment + class Analytics + describe Message do + describe '#to_json' do + it 'caches JSON conversions' do + # Keeps track of the number of times to_json was called + nested_obj = Class.new do + attr_reader :to_json_call_count + + def initialize + @to_json_call_count = 0 + end + + def to_json(*_) + @to_json_call_count += 1 + '{}' + end + end.new + + message = Message.new('some_key' => nested_obj) + expect(nested_obj.to_json_call_count).to eq(0) + + message.to_json + expect(nested_obj.to_json_call_count).to eq(1) + + # When called a second time, the call count shouldn't increase + message.to_json + expect(nested_obj.to_json_call_count).to eq(1) + end + end + end + end +end From 6682f02945050fa16a4e8d58b7bfd1c00983721c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 9 Jan 2018 18:42:40 +0530 Subject: [PATCH 180/322] Limit batch size to 500kb --- lib/segment/analytics/defaults.rb | 6 +++- lib/segment/analytics/message.rb | 6 +++- lib/segment/analytics/message_batch.rb | 34 ++++++++++++++++++-- lib/segment/analytics/worker.rb | 7 ++-- spec/segment/analytics/message_batch_spec.rb | 2 +- spec/segment/analytics/worker_spec.rb | 7 ++-- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index 18cf861..8562346 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -13,7 +13,6 @@ module Request end module Queue - BATCH_SIZE = 100 MAX_SIZE = 10000 end @@ -21,6 +20,11 @@ module Message MAX_BYTES = 32768 # 32Kb end + module MessageBatch + MAX_BYTES = 512_000 # 500Kb + MAX_SIZE = 100 + end + module BackoffPolicy MIN_TIMEOUT_MS = 100 MAX_TIMEOUT_MS = 10000 diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb index b11b121..e00a74d 100644 --- a/lib/segment/analytics/message.rb +++ b/lib/segment/analytics/message.rb @@ -9,7 +9,11 @@ def initialize(hash) end def too_big? - to_json.bytesize > Defaults::Message::MAX_BYTES + json_size > Defaults::Message::MAX_BYTES + end + + def json_size + to_json.bytesize end # Since the hash is expected to not be modified (set at initialization), diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 0cf1dc4..bc44971 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -6,9 +6,12 @@ class Analytics class MessageBatch extend Forwardable include Segment::Analytics::Logging + include Segment::Analytics::Defaults::MessageBatch - def initialize + def initialize(max_message_count) @messages = [] + @max_message_count = max_message_count + @json_size = 0 end def <<(message) @@ -16,13 +19,40 @@ def <<(message) logger.error('a message exceeded the maximum allowed size') else @messages << message + @json_size += message.json_size + 1 # One byte for the comma end end + def full? + item_count_exhausted? || size_exhausted? + end + + def clear + @messages.clear + @json_size = 0 + end + def_delegators :@messages, :to_json - def_delegators :@messages, :clear def_delegators :@messages, :empty? def_delegators :@messages, :length + + private + + def item_count_exhausted? + @messages.length >= @max_message_count + end + + # We consider the max size here as just enough to leave room for one more + # message of the largest size possible. This is a shortcut that allows us + # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff + # here is that we might fit in less messages than possible into a batch. + # + # The alternative is to use our own `Queue` implementation that allows + # peeking, and to consider the next message size when calculating whether + # the message can be accomodated in this batch. + def size_exhausted? + @json_size >= (MAX_BYTES - Defaults::Message::MAX_BYTES) + end end end end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 93e2067..c57e219 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -25,9 +25,9 @@ def initialize(queue, write_key, options = {}) symbolize_keys! options @queue = queue @write_key = write_key - @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || proc { |status, error| } - @batch = MessageBatch.new + batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE + @batch = MessageBatch.new(batch_size) @lock = Mutex.new end @@ -38,13 +38,12 @@ def run return if @queue.empty? @lock.synchronize do - until @batch.length >= @batch_size || @queue.empty? + until @batch.full? || @queue.empty? @batch << Message.new(@queue.pop) end end res = Request.new.post @write_key, @batch - @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index e50f4d0..850c9bd 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -4,7 +4,7 @@ module Segment class Analytics describe MessageBatch do describe '#<<' do - subject { described_class.new } + subject { described_class.new(100) } it 'appends messages' do subject << Message.new('a' => 'b') diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 3abd4d8..7668124 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -6,8 +6,11 @@ class Analytics describe '#init' do it 'accepts string keys' do queue = Queue.new - worker = Segment::Analytics::Worker.new(queue, 'secret', 'batch_size' => 100) - expect(worker.instance_variable_get(:@batch_size)).to eq(100) + worker = Segment::Analytics::Worker.new(queue, + 'secret', + 'batch_size' => 100) + batch = worker.instance_variable_get(:@batch) + expect(batch.instance_variable_get(:@max_message_count)).to eq(100) end end From 39232c2135f48cb98f14628c2044c8b28a148ee7 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 9 Jan 2018 18:55:21 +0530 Subject: [PATCH 181/322] Add specs for MessageBatch#full? --- spec/segment/analytics/message_batch_spec.rb | 39 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 850c9bd..562196a 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -3,16 +3,25 @@ module Segment class Analytics describe MessageBatch do - describe '#<<' do - subject { described_class.new(100) } + subject { described_class.new(100) } + describe '#<<' do it 'appends messages' do subject << Message.new('a' => 'b') expect(subject.length).to eq(1) end it 'rejects messages that exceed the maximum allowed size' do - max_bytes = Segment::Analytics::Defaults::Message::MAX_BYTES + max_bytes = Defaults::Message::MAX_BYTES + hash = { 'a' => 'b' * max_bytes } + message = Message.new(hash) + + subject << message + expect(subject.length).to eq(0) + end + + it 'rejects messages that exceed the maximum allowed size' do + max_bytes = Defaults::Message::MAX_BYTES hash = { 'a' => 'b' * max_bytes } message = Message.new(hash) @@ -20,6 +29,30 @@ class Analytics expect(subject.length).to eq(0) end end + + describe '#full?' do + it 'returns true once item count is exceeded' do + 99.times { subject << Message.new(a: 'b') } + expect(subject.full?).to be(false) + + subject << Message.new(a: 'b') + expect(subject.full?).to be(true) + end + + it 'returns true once max size is almost exceeded' do + message = Message.new(a: 'b' * (Defaults::Message::MAX_BYTES - 10)) + + # Each message is under the individual limit + expect(message.json_size).to be < Defaults::Message::MAX_BYTES + + # Size of the batch is over the limit + expect(50 * message.json_size).to be > Defaults::MessageBatch::MAX_BYTES + + expect(subject.full?).to be(false) + 50.times { subject << message } + expect(subject.full?).to be(true) + end + end end end end From c8cf2f10cdceaf7651fb02b7f2179b25d9e7c2e7 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 9 Jan 2018 19:03:24 +0530 Subject: [PATCH 182/322] Increase number of runscope messages checked for e2e tests Since builds run in parallel, the e2e tests sometimes fail because their runscope messages are pushed down by other builds. We currently test against 7 ruby versions, and a maximum of 2 builds are triggered per PR (push + PR build). Checking 20 items should be sufficient to handle this workload. --- spec/helpers/runscope_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/runscope_client.rb b/spec/helpers/runscope_client.rb index 7d0e7f8..a68be26 100644 --- a/spec/helpers/runscope_client.rb +++ b/spec/helpers/runscope_client.rb @@ -9,7 +9,7 @@ def initialize(api_token) def requests(bucket_key) with_retries(3) do - response = @conn.get("/buckets/#{bucket_key}/messages", count: 10) + response = @conn.get("/buckets/#{bucket_key}/messages", count: 20) raise "Runscope error. #{response.body}" unless response.status == 200 From 1fcc9583383bde7ffdde09b3f95e30a0c9ca2720 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 10 Jan 2018 13:53:50 +0530 Subject: [PATCH 183/322] Fix long lines in worker_spec.rb --- spec/segment/analytics/worker_spec.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 7668124..c85861a 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -38,8 +38,11 @@ class Analytics end.to_not raise_error end - it 'executes the error handler, before the request phase ends, if the request is invalid' do - Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, 'Some error')) + it 'executes the error handler if the request is invalid' do + Segment::Analytics::Request + .any_instance + .stub(:post) + .and_return(Segment::Analytics::Response.new(400, 'Some error')) status = error = nil on_error = proc do |yielded_status, yielded_error| @@ -49,9 +52,10 @@ class Analytics queue = Queue.new queue << {} - worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error + worker = described_class.new(queue, 'secret', :on_error => on_error) - # This is to ensure that Client#flush doesn't finish before calling the error handler. + # This is to ensure that Client#flush doesn't finish before calling + # the error handler. Thread.new { worker.run } sleep 0.1 # First give thread time to spin-up. sleep 0.01 while worker.is_requesting? @@ -72,7 +76,9 @@ class Analytics queue = Queue.new queue << Requested::TRACK - worker = Segment::Analytics::Worker.new queue, 'testsecret', :on_error => on_error + worker = described_class.new(queue, + 'testsecret', + :on_error => on_error) worker.run expect(queue).to be_empty From c76f94a39d66dfaad8965ac2b1e3f5a50a5fbdfc Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 10 Jan 2018 14:00:43 +0530 Subject: [PATCH 184/322] Fix indeterminate test in worker_spec.rb The assertion in the thread wouldn't trigger if the test exited first. --- spec/segment/analytics/worker_spec.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index c85861a..e66db64 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -98,12 +98,11 @@ class Analytics queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') - Thread.new do - worker.run - expect(worker.is_requesting?).to eq(false) - end - + worker_thread = Thread.new { worker.run } eventually { expect(worker.is_requesting?).to eq(true) } + + worker_thread.join + expect(worker.is_requesting?).to eq(false) end end end From 42b28619c9602d14830ed746ab96d4a1c01bb3d7 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 11 Jan 2018 23:43:38 +0530 Subject: [PATCH 185/322] Log errors for messages that exceed the maximum size (#143) * Use a custom 'Message' class * Add custom 'MessageBatch' class * Reject messages that exceed the maximum allowed size * Cache JSON generation for individual messages --- lib/segment/analytics/defaults.rb | 4 +++ lib/segment/analytics/message.rb | 22 ++++++++++++ lib/segment/analytics/message_batch.rb | 28 ++++++++++++++++ lib/segment/analytics/worker.rb | 9 ++--- spec/segment/analytics/message_batch_spec.rb | 25 ++++++++++++++ spec/segment/analytics/message_spec.rb | 35 ++++++++++++++++++++ 6 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 lib/segment/analytics/message.rb create mode 100644 lib/segment/analytics/message_batch.rb create mode 100644 spec/segment/analytics/message_batch_spec.rb create mode 100644 spec/segment/analytics/message_spec.rb diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index 550bf67..18cf861 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -17,6 +17,10 @@ module Queue MAX_SIZE = 10000 end + module Message + MAX_BYTES = 32768 # 32Kb + end + module BackoffPolicy MIN_TIMEOUT_MS = 100 MAX_TIMEOUT_MS = 10000 diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb new file mode 100644 index 0000000..b11b121 --- /dev/null +++ b/lib/segment/analytics/message.rb @@ -0,0 +1,22 @@ +require 'segment/analytics/defaults' + +module Segment + class Analytics + # Represents a message to be sent to the API + class Message + def initialize(hash) + @hash = hash + end + + def too_big? + to_json.bytesize > Defaults::Message::MAX_BYTES + end + + # Since the hash is expected to not be modified (set at initialization), + # the JSON version can be cached after the first computation. + def to_json(*args) + @json ||= @hash.to_json(*args) + end + end + end +end diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb new file mode 100644 index 0000000..0cf1dc4 --- /dev/null +++ b/lib/segment/analytics/message_batch.rb @@ -0,0 +1,28 @@ +require 'segment/analytics/logging' + +module Segment + class Analytics + # A batch of `Message`s to be sent to the API + class MessageBatch + extend Forwardable + include Segment::Analytics::Logging + + def initialize + @messages = [] + end + + def <<(message) + if message.too_big? + logger.error('a message exceeded the maximum allowed size') + else + @messages << message + end + end + + def_delegators :@messages, :to_json + def_delegators :@messages, :clear + def_delegators :@messages, :empty? + def_delegators :@messages, :length + end + end +end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f619a1b..93e2067 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,7 +1,8 @@ require 'segment/analytics/defaults' -require 'segment/analytics/utils' -require 'segment/analytics/defaults' +require 'segment/analytics/message' +require 'segment/analytics/message_batch' require 'segment/analytics/request' +require 'segment/analytics/utils' module Segment class Analytics @@ -26,7 +27,7 @@ def initialize(queue, write_key, options = {}) @write_key = write_key @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || proc { |status, error| } - @batch = [] + @batch = MessageBatch.new @lock = Mutex.new end @@ -38,7 +39,7 @@ def run @lock.synchronize do until @batch.length >= @batch_size || @queue.empty? - @batch << @queue.pop + @batch << Message.new(@queue.pop) end end diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb new file mode 100644 index 0000000..e50f4d0 --- /dev/null +++ b/spec/segment/analytics/message_batch_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +module Segment + class Analytics + describe MessageBatch do + describe '#<<' do + subject { described_class.new } + + it 'appends messages' do + subject << Message.new('a' => 'b') + expect(subject.length).to eq(1) + end + + it 'rejects messages that exceed the maximum allowed size' do + max_bytes = Segment::Analytics::Defaults::Message::MAX_BYTES + hash = { 'a' => 'b' * max_bytes } + message = Message.new(hash) + + subject << message + expect(subject.length).to eq(0) + end + end + end + end +end diff --git a/spec/segment/analytics/message_spec.rb b/spec/segment/analytics/message_spec.rb new file mode 100644 index 0000000..b156183 --- /dev/null +++ b/spec/segment/analytics/message_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +module Segment + class Analytics + describe Message do + describe '#to_json' do + it 'caches JSON conversions' do + # Keeps track of the number of times to_json was called + nested_obj = Class.new do + attr_reader :to_json_call_count + + def initialize + @to_json_call_count = 0 + end + + def to_json(*_) + @to_json_call_count += 1 + '{}' + end + end.new + + message = Message.new('some_key' => nested_obj) + expect(nested_obj.to_json_call_count).to eq(0) + + message.to_json + expect(nested_obj.to_json_call_count).to eq(1) + + # When called a second time, the call count shouldn't increase + message.to_json + expect(nested_obj.to_json_call_count).to eq(1) + end + end + end + end +end From 8f987c7568059a66d2a33b27c7594241dcee16b3 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 11 Jan 2018 23:43:49 +0530 Subject: [PATCH 186/322] Add logging if queue is full (#145) --- .rubocop_todo.yml | 2 +- lib/segment/analytics/client.rb | 12 ++++++++++-- spec/segment/analytics/client_spec.rb | 10 ++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8057bd2..b2a25cb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,7 +18,7 @@ Metrics/AbcSize: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 223 + Max: 229 # Offense count: 1 Metrics/CyclomaticComplexity: diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index b216c7a..0475dda 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -1,13 +1,16 @@ require 'thread' require 'time' + +require 'segment/analytics/defaults' +require 'segment/analytics/logging' require 'segment/analytics/utils' require 'segment/analytics/worker' -require 'segment/analytics/defaults' module Segment class Analytics class Client include Segment::Analytics::Utils + include Segment::Analytics::Logging # public: Creates a new client # @@ -314,7 +317,12 @@ def enqueue(action) true else - false # Queue is full + logger.warn( + 'Queue is full, dropping events. The :max_queue_size ' \ + 'configuration parameter can be increased to prevent this from ' \ + 'happening.' + ) + false end end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index ebc9a0a..f8a52e3 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -275,6 +275,16 @@ class Analytics end end + it 'returns false if queue is full' do + client.instance_variable_set(:@max_queue_size, 1) + + [:track, :screen, :page, :group, :identify, :alias].each do |s| + expect(client.send(s, data)).to eq(true) + expect(client.send(s, data)).to eq(false) # Queue is full + queue.pop(true) + end + end + it 'converts message id to string' do [:track, :screen, :page, :group, :identify, :alias].each do |s| client.send(s, data) From 204d5a27bdde62c130b2a575c78d6e0ccd6f0ef8 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 11 Jan 2018 23:44:13 +0530 Subject: [PATCH 187/322] Reuse TCP connections (#149) --- lib/segment/analytics/request.rb | 5 +++++ lib/segment/analytics/worker.rb | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index d077a3a..502d8e1 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -112,6 +112,11 @@ def send_request(write_key, batch) [200, '{}'] else + # If `start` is not called, Ruby adds a 'Connection: close' header to + # all requests, preventing us from reusing a connection for multiple + # HTTP requests + @http.start unless @http.started? + response = @http.request(request, payload) [response.code.to_i, response.body] end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 93e2067..a5fb638 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -29,6 +29,7 @@ def initialize(queue, write_key, options = {}) @on_error = options[:on_error] || proc { |status, error| } @batch = MessageBatch.new @lock = Mutex.new + @request = Request.new end # public: Continuously runs the loop to check for new events @@ -43,8 +44,7 @@ def run end end - res = Request.new.post @write_key, @batch - + res = @request.post(@write_key, @batch) @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } From 1c63a85fbbf45c0293c2b0a0e0fc4e26ba3bf2b3 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 12 Jan 2018 22:13:36 +0530 Subject: [PATCH 188/322] Cleanup docs (#150) * Cleanup docs for Segment::Analytics::Client * Add docs for top-level Analytics class --- .rubocop_todo.yml | 2 +- lib/segment/analytics.rb | 7 ++ lib/segment/analytics/client.rb | 198 +++++++++++++++++++------------- 3 files changed, 125 insertions(+), 82 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b2a25cb..17dd033 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,7 +18,7 @@ Metrics/AbcSize: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 229 + Max: 231 # Offense count: 1 Metrics/CyclomaticComplexity: diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 0105c16..13ca1ec 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -9,6 +9,13 @@ module Segment class Analytics + # Initializes a new instance of {Segment::Analytics::Client}, to which all + # method calls are proxied. + # + # @param options includes options that are passed down to + # {Segment::Analytics::Client#initialize} + # @option options [Boolean] :stub (false) If true, requests don't hit the + # server and are stubbed to be successful. def initialize(options = {}) Request.stub = options[:stub] if options.has_key?(:stub) @client = Segment::Analytics::Client.new options diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 0475dda..ce830a3 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -12,31 +12,30 @@ class Client include Segment::Analytics::Utils include Segment::Analytics::Logging - # public: Creates a new client - # - # attrs - Hash - # :write_key - String of your project's write_key - # :max_queue_size - Fixnum of the max calls to remain queued (optional) - # :on_error - Proc which handles error calls from the API - def initialize(attrs = {}) - symbolize_keys! attrs + # @param [Hash] opts + # @option opts [String] :write_key Your project's write_key + # @option opts [FixNum] :max_queue_size Maximum number of calls to be + # remain queued. + # @option opts [Proc] :on_error Handles error calls from the API. + def initialize(opts = {}) + symbolize_keys!(opts) @queue = Queue.new - @write_key = attrs[:write_key] - @max_queue_size = attrs[:max_queue_size] || Defaults::Queue::MAX_SIZE - @options = attrs + @write_key = opts[:write_key] + @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE + @options = opts @worker_mutex = Mutex.new - @worker = Worker.new @queue, @write_key, @options + @worker = Worker.new(@queue, @write_key, @options) check_write_key! at_exit { @worker_thread && @worker_thread[:should_exit] = true } end - # public: Synchronously waits until the worker has flushed the queue. - # Use only for scripts which are not long-running, and will - # specifically exit + # Synchronously waits until the worker has flushed the queue. # + # Use only for scripts which are not long-running, and will specifically + # exit def flush while !@queue.empty? || @worker.is_requesting? ensure_worker_running @@ -44,18 +43,25 @@ def flush end end - # public: Tracks an event + # Tracks an event + # + # @see https://segment.com/docs/sources/server/ruby/#track # - # attrs - Hash - # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) - # :context - Hash of context. (optional) - # :event - String of event name. - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :options - Hash specifying options such as user traits. (optional) - # :properties - Hash of event properties. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :user_id - String of the user id. - # :message_id - String of the message id that uniquely identified a message across the API. (optional) + # @param [Hash] attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [Hash] :context ({}) + # @option attrs [String] :event Event name + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [Hash] :properties Event properties (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) def track(attrs) symbolize_keys! attrs check_user_id! attrs @@ -91,17 +97,24 @@ def track(attrs) }) end - # public: Identifies a user + # Identifies a user # - # attrs - Hash - # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) - # :context - Hash of context. (optional) - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :options - Hash specifying options such as user traits. (optional) - # :timestamp - Time of when the event occurred. (optional) - # :traits - Hash of user traits. (optional) - # :user_id - String of the user id - # :message_id - String of the message id that uniquely identified a message across the API. (optional) + # @see https://segment.com/docs/sources/server/ruby/#identify + # + # @param [Hash] attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [Hash] :context ({}) + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [Hash] :traits User traits (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) + # @option attrs [String] :message_id ID that uniquely identifies a + # message across the API. (optional) def identify(attrs) symbolize_keys! attrs check_user_id! attrs @@ -131,16 +144,20 @@ def identify(attrs) }) end - # public: Aliases a user from one id to another + # Aliases a user from one id to another + # + # @see https://segment.com/docs/sources/server/ruby/#alias # - # attrs - Hash - # :context - Hash of context (optional) - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :options - Hash specifying options such as user traits. (optional) - # :previous_id - String of the id to alias from - # :timestamp - Time of when the alias occured (optional) - # :user_id - String of the id to alias to - # :message_id - String of the message id that uniquely identified a message across the API. (optional) + # @param [Hash] attrs + # @option attrs [Hash] :context ({}) + # @option attrs [Hash] :integrations What integrations this must be + # sent to (optional) + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [String] :previous_id The ID to alias from + # @option attrs [Time] :timestamp When the alias occurred (optional) + # @option attrs [String] :user_id The ID to alias to + # @option attrs [String] :message_id ID that uniquely identifies a + # message across the API. (optional) def alias(attrs) symbolize_keys! attrs @@ -167,16 +184,24 @@ def alias(attrs) }) end - # public: Associates a user identity with a group. + # Associates a user identity with a group. # - # attrs - Hash - # :context - Hash of context (optional) - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :options - Hash specifying options such as user traits. (optional) - # :previous_id - String of the id to alias from - # :timestamp - Time of when the alias occured (optional) - # :user_id - String of the id to alias to - # :message_id - String of the message id that uniquely identified a message across the API. (optional) + # @see https://segment.com/docs/sources/server/ruby/#group + # + # @param [Hash] attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [Hash] :context ({}) + # @option attrs [String] :group_id The ID of the group + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for the user that is part of + # the group + # @option attrs [String] :message_id ID that uniquely identifies a + # message across the API. (optional) def group(attrs) symbolize_keys! attrs check_user_id! attrs @@ -208,19 +233,25 @@ def group(attrs) }) end - # public: Records a page view + # Records a page view + # + # @see https://segment.com/docs/sources/server/ruby/#page # - # attrs - Hash - # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) - # :category - String of the page category (optional) - # :context - Hash of context (optional) - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :name - String name of the page - # :options - Hash specifying options such as user traits. (optional) - # :properties - Hash of page properties (optional) - # :timestamp - Time of when the pageview occured (optional) - # :user_id - String of the id to alias from - # :message_id - String of the message id that uniquely identified a message across the API. (optional) + # @param [Hash] attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [String] :category The page category (optional) + # @option attrs [Hash] :context ({}) + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [String] :name Name of the page + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [Hash] :properties Page properties (optional) + # @option attrs [Time] :timestamp When the pageview occurred (optional) + # @option attrs [String] :user_id The ID of the user viewing the page + # @option attrs [String] :message_id ID that uniquely identifies a + # message across the API. (optional) def page(attrs) symbolize_keys! attrs check_user_id! attrs @@ -252,18 +283,23 @@ def page(attrs) }) end - # public: Records a screen view (for a mobile app) + # Records a screen view (for a mobile app) # - # attrs - Hash - # :anonymous_id - String of the user's id when you don't know who they are yet. (optional but you must provide either an anonymous_id or user_id. See: https://segment.io/docs/tracking - api/track/#user - id) - # :category - String screen category (optional) - # :context - Hash of context (optional) - # :integrations - Hash specifying what integrations this event goes to. (optional) - # :name - String name of the screen - # :options - Hash specifying options such as user traits. (optional) - # :properties - Hash of screen properties (optional) - # :timestamp - Time of when the screen occured (optional) - # :user_id - String of the id to alias from + # @param [Hash] attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [String] :category The screen category (optional) + # @option attrs [Hash] :context ({}) + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [String] :name Name of the screen + # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [Hash] :properties Page properties (optional) + # @option attrs [Time] :timestamp When the pageview occurred (optional) + # @option attrs [String] :user_id The ID of the user viewing the screen + # @option attrs [String] :message_id ID that uniquely identifies a + # message across the API. (optional) def screen(attrs) symbolize_keys! attrs check_user_id! attrs @@ -295,9 +331,7 @@ def screen(attrs) }) end - # public: Returns the number of queued messages - # - # returns Fixnum of messages in the queue + # @return [Fixnum] number of messages in the queue def queued_messages @queue.length end @@ -368,7 +402,9 @@ def event(attrs) end def check_user_id!(attrs) - raise ArgumentError, 'Must supply either user_id or anonymous_id' unless attrs[:user_id] || attrs[:anonymous_id] + unless attrs[:user_id] || attrs[:anonymous_id] + raise ArgumentError, 'Must supply either user_id or anonymous_id' + end end def ensure_worker_running From 5aea697248d312be57d7b967c1cf88672cec3e78 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 16 Jan 2018 11:32:09 +0530 Subject: [PATCH 189/322] Add Makefile --- Makefile | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5082529..a027075 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,17 @@ - -test: - rake spec - -build: - gem build ./analytics-ruby.gemspec - -.PHONY: test build + +# Install any tools required to build this library, e.g. Ruby, Bundler etc. +bootstrap: + brew install ruby + gem install bundler + +# Install any library dependencies. +dependencies: + bundle install --verbose + +# Run all tests and checks (including linters). +check: + bundle exec rake + +# Compile the code and produce any binaries where applicable. +build: + gem build ./analytics-ruby.gemspec From 93a0f285d8cec01358246c3e6080d3311b433239 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 17 Jan 2018 19:55:20 +0530 Subject: [PATCH 190/322] Remove bundle install travis step, use makefile for test script --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0638b59..69c91be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,4 @@ rvm: - 2.3.5 - 2.4.2 -before_install: - - gem install bundler +script: make check From 86683acc744dd0dfc555c0d20771688e568d6550 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 4 Feb 2018 17:04:37 +0530 Subject: [PATCH 191/322] Deploy tagged commits to RubyGems --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 69c91be..abeae2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,3 +10,10 @@ rvm: - 2.4.2 script: make check + +# Deploy tagged commits to Rubygems +# See https://docs.travis-ci.com/user/deployment/rubygems/ for more details +deploy: + provider: rubygems + on: + tags: true From 4f8ca5b419b8549b873bae38d7b191ee3b3257ad Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 4 Feb 2018 17:20:39 +0530 Subject: [PATCH 192/322] Update version and History.md for 2.2.4.pre --- History.md | 16 ++++++++++++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 7dfa15c..29315a0 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,19 @@ +2.2.4.pre / 2018-02-04 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/147): Prevent 'batch + size exceeded' errors by automatically batching + items according to size + * [Performance](https://github.com/segmentio/analytics-ruby/pull/149): Reuse + TCP connections + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/145): Emit logs + when in-memory queue is full + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/143): Emit logs + when messages exceed maximum allowed size + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/134): Add + exponential backoff to retries + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/132): Handle + HTTP status code failure appropriately 2.2.3.pre / 2017-09-14 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index df7b3d0..8a55e5a 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.3.pre' + VERSION = '2.2.4.pre' end end From ded04b0cf8fb5dd8475ae6e1480a69bd3809afa2 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 4 Feb 2018 17:26:12 +0530 Subject: [PATCH 193/322] Update RELEASING.md --- RELEASING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 0edd12b..4c54840 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -5,6 +5,5 @@ Releasing 2. Bump version in [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). 3. Update [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). 4. Commit and tag `git commit -am "Release {version}" && git tag -a {version} -m "Version {version}"`. - 5. Build the gem with the tagged version `make build`. - 6. Upload to RubyGems with `gem push analytics-ruby-{version}.gem`. - 7. Upload to Github with `git push -u origin master && git push --tags`. + 5. Upload to Github with `git push -u origin master && git push --tags`. +The tagged commit will be pushed to RubyGems via Travis. From d8a645d316810a831fb25da055653e4bac547bdd Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Tue, 13 Feb 2018 17:32:42 -0800 Subject: [PATCH 194/322] Add RubyGems API key --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 69c91be..9913504 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,3 +10,7 @@ rvm: - 2.4.2 script: make check + +deploy: + api_key: + secure: Ceq6J4aBpsoqRfSiC7z+/J4moOXNjcPMFb2Bfm5qE51cIZzeyuOIOc6zhrad9tUgoX6uTRRxLxkybyu4wNYSluMA3IXW20CJyXZeJEHIaTYIDTWFAIYyerBJyMujJycSo7XueWb0faKBENrBQKx1K1tS0EiXpA2rMhdA6RM3DOY= From ffd69ea92e15e8bcb51d53055b2f7231583c9d45 Mon Sep 17 00:00:00 2001 From: Nicolas Leger Date: Thu, 15 Feb 2018 15:09:26 +0100 Subject: [PATCH 195/322] [CI] Test against Ruby 2.5 --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c12531f..f0571ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,10 @@ rvm: - 1.9.3 - 2.0.0 - 2.1.10 - - 2.2.8 - - 2.3.5 - - 2.4.2 + - 2.2.9 + - 2.3.6 + - 2.4.3 + - 2.5.0 script: make check From d6c4e7f9b00940fe5b74519352b9ff5b61f1d3e4 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Mon, 5 Mar 2018 14:00:08 -0800 Subject: [PATCH 196/322] Update analytics binary to be compatible with the testing harness. https://paper.dropbox.com/doc/Libraries-End-to-End-Testing-Harness-dnotyIVIGYR7lnO1LcZtM#:uid=457845578223014078216812&h2=Contract Main changes are: * writeKey is a flag instead of an env var * method is a flag, instead of a top level command * context, properties, traits etc are optional Also fixed a bug with `screen` in the binary. Previously it was invoking `identify` instead of screen. --- bin/analytics | 189 +++++++++++++++++--------------------------------- 1 file changed, 65 insertions(+), 124 deletions(-) diff --git a/bin/analytics b/bin/analytics index e7543cb..e9e107e 100755 --- a/bin/analytics +++ b/bin/analytics @@ -10,143 +10,84 @@ program :name, 'simulator.rb' program :version, '0.0.1' program :description, 'scripting simulator' -# use an env var for write key, instead of a flag -Analytics = Segment::Analytics.new({ - write_key: ENV['SEGMENT_WRITE_KEY'], - on_error: Proc.new { |status, msg| print msg } -}) - -def toObject(str) - return JSON.parse(str) -end - -# high level -# analytics [options] -# SEGMENT_WRITE_KEY= ./analytics.rb [options] -# SEGMENT_WRITE_KEY= ./analytics.rb track --event testing --user 1234 --anonymous 567 --properties '{"hello": "goodbye"}' --context '{"slow":"poke"}' - - - -# track -command :track do |c| - c.description = 'track a user event' - c.option '--user ', String, 'the user id to send the event as' - c.option '--event ', String, 'the event name to send with the event' - c.option '--anonymous ', String, 'the anonymous user id to send the event as' - c.option '--properties ', 'the event properties to send (JSON-encoded)' - c.option '--context ', 'additional context for the event (JSON-encoded)' - - c.action do |args, options| - Analytics.track({ - user_id: options.user, - event: options.event, - anonymous_id: options.anonymous, - properties: toObject(options.properties), - context: toObject(options.context) - }) - Analytics.flush +def json_hash(str) + if str + return JSON.parse(str) end - end -# page -command :page do |c| - c.description = 'track a page view' - c.option '--user ', String, 'the user id to send the event as' - c.option '--anonymous ', String, 'the anonymous user id to send the event as' - c.option '--name ', String, 'the page name' - c.option '--properties ', 'the event properties to send (JSON-encoded)' - c.option '--context ', 'additional context for the event (JSON-encoded)' - c.option '--category ', 'the category of the page' - c.action do |args, options| - Analytics.page({ - user_id: options.user, - anonymous_id: options.anonymous, - name: options.name, - properties: toObject(options.properties), - context: toObject(options.context), - category: options.category - }) - Analytics.flush - end +# analytics -method= -segment-write-key= [options] -end +default_command :send -# identify -command :identify do |c| - c.description = 'identify a user' - c.option '--user ', String, 'the user id to send the event as' - c.option '--anonymous ', String, 'the anonymous user id to send the event as' - c.option '--traits ', String, 'the user traits to send (JSON-encoded)' - c.option '--context ', 'additional context for the event (JSON-encoded)' - c.action do |args, options| - Analytics.identify({ - user_id: options.user, - anonymous_id: options.anonymous, - traits: toObject(options.traits), - context: toObject(options.context) - }) - Analytics.flush - end +command :send do |c| + c.description = 'send a segment message' -end + c.option '--writeKey=', String, 'the Segment writeKey' + c.option '--type=', String, 'The Segment message type' -# screen -command :screen do |c| - c.description = 'track a screen view' - c.option '--user ', String, 'the user id to send the event as' - c.option '--anonymous ', String, 'the anonymous user id to send the event as' - c.option '--name ', String, 'the screen name' - c.option '--properties ', String, 'the event properties to send (JSON-encoded)' - c.option '--context ', 'additional context for the event (JSON-encoded)' - c.action do |args, options| - Analytics.identify({ - user_id: options.user, - anonymous_id: options.anonymous, - name: option.name, - traits: toObject(options.traits), - properties: toObject(option.properties), - context: toObject(options.context) - }) - Analytics.flush - end + c.option '--userId=', String, 'the user id to send the event as' + c.option '--anonymousId=', String, 'the anonymous user id to send the event as' + c.option '--context=', 'additional context for the event (JSON-encoded)' -end + c.option '--event=', String, 'the event name to send with the event' + c.option '--properties=', 'the event properties to send (JSON-encoded)' + c.option '--name=', 'name of the screen or page to send with the message' -# group -command :group do |c| - c.description = 'identify a group of users' - c.option '--user ', String, 'the user id to send the event as' - c.option '--anonymous ', String, 'the anonymous user id to send the event as' - c.option '--group ', String, 'the group id to associate this user with' - c.option '--traits ', String, 'attributes about the group (JSON-encoded)' - c.option '--context ', 'additional context for the event (JSON-encoded)' - c.action do |args, options| - Analytics.group({ - user_id: options.user, - anonymous_id: options.anonymous, - group_id: options.group, - traits: toObject(options.traits), - context: toObject(options.context) - }) - Analytics.flush - end + c.option '--traits=', 'the identify/group traits to send (JSON-encoded)' -end + c.option '--groupId=', String, 'the group id' -# alias -command :alias do |c| - c.description = 'remap a user to a new id' - c.option '--user ', String, 'the user id to send the event as' - c.option '--previous ', String, 'the previous user id (to add the alias for)' c.action do |args, options| - Analytics.alias({ - user_id: options.user, - previous_id: options.previous - }) + Analytics = Segment::Analytics.new({ + write_key: options.writeKey, + on_error: Proc.new { |status, msg| print msg } + }) + + case options.type + when "track" + Analytics.track({ + user_id: options.userId, + event: options.event, + anonymous_id: options.anonymousId, + properties: json_hash(options.properties), + context: json_hash(options.context) + }) + when "page" + Analytics.page({ + user_id: options.userId, + anonymous_id: options.anonymousId, + name: options.name, + properties: json_hash(options.properties), + context: json_hash(options.context) + }) + when "screen" + Analytics.screen({ + user_id: options.userId, + anonymous_id: options.anonymousId, + name: option.name, + traits: json_hash(options.traits), + properties: json_hash(option.properties) + }) + when "identify" + Analytics.identify({ + user_id: options.userId, + anonymous_id: options.anonymousId, + traits: json_hash(options.traits), + context: json_hash(options.context) + }) + when "group" + Analytics.group({ + user_id: options.userId, + anonymous_id: options.anonymousId, + group_id: options.groupId, + traits: json_hash(options.traits), + context: json_hash(options.context) + }) + else + raise "Invalid Message Type #{options.type}" + end Analytics.flush end - end - From c9c6a156d8f9a6e85362be5c93276f21bf08d570 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Mon, 30 Apr 2018 16:30:29 -0700 Subject: [PATCH 197/322] Release 2.2.4 --- History.md | 7 ++++++- lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 29315a0..0797770 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,11 @@ -2.2.4.pre / 2018-02-04 +2.2.4 / 2018-04-30 ================== + * Promote pre-release version to stable. + +2.2.4.pre / 2018-02-04 +====================== + * [Fix](https://github.com/segmentio/analytics-ruby/pull/147): Prevent 'batch size exceeded' errors by automatically batching items according to size diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 8a55e5a..3fd4098 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.4.pre' + VERSION = '2.2.4' end end From dbc6ae08fc4410bcc3f01e05c6e3c3fb1c859a5b Mon Sep 17 00:00:00 2001 From: Tomasz Pajor Date: Tue, 1 May 2018 11:53:58 +0200 Subject: [PATCH 198/322] require version first as it is used in the defaults now --- lib/segment/analytics.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 13ca1ec..c03f8cc 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -1,6 +1,6 @@ +require 'segment/analytics/version' require 'segment/analytics/defaults' require 'segment/analytics/utils' -require 'segment/analytics/version' require 'segment/analytics/client' require 'segment/analytics/worker' require 'segment/analytics/request' From 927e70c2d5e603f75a06e72ff782c3f43b7ff610 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Tue, 1 May 2018 10:16:47 -0700 Subject: [PATCH 199/322] Release 2.2.5 --- History.md | 5 +++++ RELEASING.md | 2 +- lib/segment/analytics/version.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 0797770..2c38a4b 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.2.5 / 2018-05-01 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/158): Require `version` module first. + 2.2.4 / 2018-04-30 ================== diff --git a/RELEASING.md b/RELEASING.md index 4c54840..2a60d9c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,7 +1,7 @@ Releasing ========= - 1. Verify everything works with `make test build`. + 1. Verify everything works with `make check build`. 2. Bump version in [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). 3. Update [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). 4. Commit and tag `git commit -am "Release {version}" && git tag -a {version} -m "Version {version}"`. diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 3fd4098..258a714 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.4' + VERSION = '2.2.5' end end From 293d224eee54326ebd2d614f290ca549f29375bf Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 2 May 2018 02:25:44 +0530 Subject: [PATCH 200/322] Avoid rubygems race condition with parallel pushes Earlier, all our Travis build jobs (one for each ruby version) used to attempt to push a tagged version to Rubygems. The idea here was that one of these would succeed and all the other would fail saying 'repushing versions isn't allowed'. Turns out, there's a race condition in Rubygems in the controller we're hitting. Details: https://github.com/rubygems/rubygems.org/issues/1551 This commits instructs Travis to deploy only on a single build, so that we're very unlikely to trigger said race condition. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f0571ec..d6fc9ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ rvm: - 2.2.9 - 2.3.6 - 2.4.3 - - 2.5.0 + - 2.5.0 # Performs deploys. Change condition below when changing this. script: make check @@ -18,5 +18,6 @@ deploy: provider: rubygems on: tags: true + condition: "$TRAVIS_RUBY_VERSION == 2.5.0" api_key: secure: Ceq6J4aBpsoqRfSiC7z+/J4moOXNjcPMFb2Bfm5qE51cIZzeyuOIOc6zhrad9tUgoX6uTRRxLxkybyu4wNYSluMA3IXW20CJyXZeJEHIaTYIDTWFAIYyerBJyMujJycSo7XueWb0faKBENrBQKx1K1tS0EiXpA2rMhdA6RM3DOY= From c135f87d46721eb1e8d44f70028642625692c2bf Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 15 May 2018 10:22:48 +0530 Subject: [PATCH 201/322] Add missing forwardable requirement This is unlikely to occur on most setups because another gem must've 'required' forwardable already --- lib/segment/analytics/message_batch.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index bc44971..1864f16 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -1,3 +1,4 @@ +require 'forwardable' require 'segment/analytics/logging' module Segment From b55df2695b6f745cf126e5622a87503e67904da3 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 19 May 2018 16:09:29 +0530 Subject: [PATCH 202/322] Add [analytics-ruby] prefix to all log messages --- lib/segment/analytics/logging.rb | 41 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/segment/analytics/logging.rb b/lib/segment/analytics/logging.rb index 20b1a7f..f7aaa63 100644 --- a/lib/segment/analytics/logging.rb +++ b/lib/segment/analytics/logging.rb @@ -2,16 +2,43 @@ module Segment class Analytics + # Wraps an existing logger and adds a prefix to all messages + class PrefixedLogger + def initialize(logger, prefix) + @logger = logger + @prefix = prefix + end + + def debug(msg) + @logger.debug("#{@prefix} #{msg}") + end + + def info(msg) + @logger.info("#{@prefix} #{msg}") + end + + def warn(msg) + @logger.warn("#{@prefix} #{msg}") + end + + def error(msg) + @logger.error("#{@prefix} #{msg}") + end + end + module Logging class << self def logger - @logger ||= if defined?(Rails) - Rails.logger - else - logger = Logger.new STDOUT - logger.progname = 'Segment::Analytics' - logger - end + return @logger if @logger + + base_logger = if defined?(Rails) + Rails.logger + else + logger = Logger.new STDOUT + logger.progname = 'Segment::Analytics' + logger + end + @logger = PrefixedLogger.new(base_logger, '[analytics-ruby]') end attr_writer :logger From 4463ca5e82482e29b1e65557847b1d4d52a70da4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 19 May 2018 16:12:45 +0530 Subject: [PATCH 203/322] Add debug log when retrying request --- lib/segment/analytics/request.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 502d8e1..0cdf476 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -90,6 +90,7 @@ def retry_with_backoff(retries_remaining, &block) end if should_retry && (retries_remaining > 1) + logger.debug("Retrying request, #{retries_remaining} retries left") sleep(@backoff_policy.next_interval.to_f / 1000) retry_with_backoff(retries_remaining - 1, &block) else From c52c386a2b7631490c3c4d4e88d2ce4d7d61c891 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 19 May 2018 16:14:24 +0530 Subject: [PATCH 204/322] Add debug log for number of items sent in each call --- lib/segment/analytics/worker.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index f1c7ef4..7f1a286 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -9,6 +9,7 @@ class Analytics class Worker include Segment::Analytics::Utils include Segment::Analytics::Defaults + include Segment::Analytics::Logging # public: Creates a new worker # @@ -44,6 +45,7 @@ def run end end + logger.debug("Sending request for #{@batch.length} items") res = @request.post(@write_key, @batch) @on_error.call(res.status, res.error) unless res.status == 200 From 34cabffc0e80fb59b668df766736c27f69e5e3d4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 23 May 2018 17:50:38 +0530 Subject: [PATCH 205/322] Add HTTP status code to debug logs --- lib/segment/analytics/request.rb | 4 ++++ lib/segment/analytics/worker.rb | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 0cdf476..f1a319b 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -38,10 +38,14 @@ def initialize(options = {}) # # returns - Response of the status and error if it exists def post(write_key, batch) + logger.debug("Sending request for #{batch.length} items") + last_response, exception = retry_with_backoff(@retries) do status_code, body = send_request(write_key, batch) error = JSON.parse(body)['error'] should_retry = should_retry_request?(status_code, body) + logger.debug("Response status code: #{status_code}") + logger.debug("Response error: #{error}") if error [Response.new(status_code, error), should_retry] end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 7f1a286..fd47703 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -45,7 +45,6 @@ def run end end - logger.debug("Sending request for #{@batch.length} items") res = @request.post(@write_key, @batch) @on_error.call(res.status, res.error) unless res.status == 200 From aa7aa8803ef358ee9a5579e1abf2984c39db8aca Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 12 Jun 2018 17:20:06 +0530 Subject: [PATCH 206/322] Add tests for oj/rails conflict --- Rakefile | 12 +++++++++++- analytics-ruby.gemspec | 1 + spec/isolated/json_example.rb | 9 +++++++++ spec/isolated/with_active_support.rb | 11 +++++++++++ spec/isolated/with_active_support_and_oj.rb | 14 ++++++++++++++ spec/isolated/with_oj.rb | 11 +++++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 spec/isolated/json_example.rb create mode 100644 spec/isolated/with_active_support.rb create mode 100644 spec/isolated/with_active_support_and_oj.rb create mode 100644 spec/isolated/with_oj.rb diff --git a/Rakefile b/Rakefile index 626fcbe..838c39f 100644 --- a/Rakefile +++ b/Rakefile @@ -3,12 +3,22 @@ require 'rspec/core/rake_task' default_tasks = [] RSpec::Core::RakeTask.new(:spec) do |spec| - spec.pattern = 'spec/**/*_spec.rb' + spec.pattern = 'spec/segment/**/*_spec.rb' spec.rspec_opts = "--tag ~e2e" if ENV["RUN_E2E_TESTS"] != "true" end default_tasks << :spec +# Isolated tests are run as separate rake tasks so that gem conflicts can be +# tests in different processes +Dir.glob('spec/isolated/**/*.rb').each do |isolated_test_path| + RSpec::Core::RakeTask.new(isolated_test_path) do |spec| + spec.pattern = isolated_test_path + end + + default_tasks << isolated_test_path +end + # Rubocop doesn't support < 2.1 if RUBY_VERSION >= "2.1" require 'rubocop/rake_task' diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 1670cf2..581f552 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'activesupport', '~> 4.1.11' spec.add_development_dependency 'faraday', '~> 0.13' spec.add_development_dependency 'pmap', '~> 1.1' + spec.add_development_dependency 'oj', '~> 3.6.2' if RUBY_VERSION >= "2.1" spec.add_development_dependency 'rubocop', '~> 0.51.0' diff --git a/spec/isolated/json_example.rb b/spec/isolated/json_example.rb new file mode 100644 index 0000000..9392e9a --- /dev/null +++ b/spec/isolated/json_example.rb @@ -0,0 +1,9 @@ +RSpec.shared_examples 'message_batch_json' do + it 'MessageBatch generates proper JSON' do + batch = Segment::Analytics::MessageBatch.new(100) + batch << Segment::Analytics::Message.new('a' => 'b') + batch << Segment::Analytics::Message.new('c' => 'd') + + expect(JSON.generate(batch)).to eq('[{"a":"b"},{"c":"d"}]') + end +end diff --git a/spec/isolated/with_active_support.rb b/spec/isolated/with_active_support.rb new file mode 100644 index 0000000..86178ae --- /dev/null +++ b/spec/isolated/with_active_support.rb @@ -0,0 +1,11 @@ +require 'spec_helper' +require 'isolated/json_example' + +describe 'with active_support' do + before do + require 'active_support' + require 'active_support/json' + end + + include_examples 'message_batch_json' +end diff --git a/spec/isolated/with_active_support_and_oj.rb b/spec/isolated/with_active_support_and_oj.rb new file mode 100644 index 0000000..92d9a74 --- /dev/null +++ b/spec/isolated/with_active_support_and_oj.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require 'isolated/json_example' + +describe 'with active_support and oj' do + before do + require 'active_support' + require 'active_support/json' + + require 'oj' + Oj.mimic_JSON + end + + include_examples 'message_batch_json' +end diff --git a/spec/isolated/with_oj.rb b/spec/isolated/with_oj.rb new file mode 100644 index 0000000..56b79ae --- /dev/null +++ b/spec/isolated/with_oj.rb @@ -0,0 +1,11 @@ +require 'spec_helper' +require 'isolated/json_example' + +describe 'with oj' do + before do + require 'oj' + Oj.mimic_JSON + end + + include_examples 'message_batch_json' +end From f860f27730de878c227dda9669a5bbca7c3ce372 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Jun 2018 18:11:58 +0530 Subject: [PATCH 207/322] Remove intermediary message class --- lib/segment/analytics/message.rb | 26 --------------- lib/segment/analytics/message_batch.rb | 10 ++++-- lib/segment/analytics/worker.rb | 5 +-- spec/isolated/json_example.rb | 4 +-- spec/segment/analytics/message_batch_spec.rb | 17 +++++----- spec/segment/analytics/message_spec.rb | 35 -------------------- 6 files changed, 20 insertions(+), 77 deletions(-) delete mode 100644 lib/segment/analytics/message.rb delete mode 100644 spec/segment/analytics/message_spec.rb diff --git a/lib/segment/analytics/message.rb b/lib/segment/analytics/message.rb deleted file mode 100644 index e00a74d..0000000 --- a/lib/segment/analytics/message.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'segment/analytics/defaults' - -module Segment - class Analytics - # Represents a message to be sent to the API - class Message - def initialize(hash) - @hash = hash - end - - def too_big? - json_size > Defaults::Message::MAX_BYTES - end - - def json_size - to_json.bytesize - end - - # Since the hash is expected to not be modified (set at initialization), - # the JSON version can be cached after the first computation. - def to_json(*args) - @json ||= @hash.to_json(*args) - end - end - end -end diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 1864f16..2e9385d 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -16,11 +16,13 @@ def initialize(max_message_count) end def <<(message) - if message.too_big? + message_json_size = message.to_json.bytesize + + if message_too_big?(message_json_size) logger.error('a message exceeded the maximum allowed size') else @messages << message - @json_size += message.json_size + 1 # One byte for the comma + @json_size += message_json_size + 1 # One byte for the comma end end @@ -43,6 +45,10 @@ def item_count_exhausted? @messages.length >= @max_message_count end + def message_too_big?(message_json_size) + message_json_size > Defaults::Message::MAX_BYTES + end + # We consider the max size here as just enough to leave room for one more # message of the largest size possible. This is a shortcut that allows us # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index fd47703..8652a53 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,5 +1,4 @@ require 'segment/analytics/defaults' -require 'segment/analytics/message' require 'segment/analytics/message_batch' require 'segment/analytics/request' require 'segment/analytics/utils' @@ -40,9 +39,7 @@ def run return if @queue.empty? @lock.synchronize do - until @batch.full? || @queue.empty? - @batch << Message.new(@queue.pop) - end + @batch << @queue.pop until @batch.full? || @queue.empty? end res = @request.post(@write_key, @batch) diff --git a/spec/isolated/json_example.rb b/spec/isolated/json_example.rb index 9392e9a..c693b55 100644 --- a/spec/isolated/json_example.rb +++ b/spec/isolated/json_example.rb @@ -1,8 +1,8 @@ RSpec.shared_examples 'message_batch_json' do it 'MessageBatch generates proper JSON' do batch = Segment::Analytics::MessageBatch.new(100) - batch << Segment::Analytics::Message.new('a' => 'b') - batch << Segment::Analytics::Message.new('c' => 'd') + batch << { 'a' => 'b' } + batch << { 'c' => 'd' } expect(JSON.generate(batch)).to eq('[{"a":"b"},{"c":"d"}]') end diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 6e014c8..4b7b66f 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -7,14 +7,13 @@ class Analytics describe '#<<' do it 'appends messages' do - subject << Message.new('a' => 'b') + subject << { 'a' => 'b' } expect(subject.length).to eq(1) end it 'rejects messages that exceed the maximum allowed size' do max_bytes = Defaults::Message::MAX_BYTES - hash = { 'a' => 'b' * max_bytes } - message = Message.new(hash) + message = { 'a' => 'b' * max_bytes } subject << message expect(subject.length).to eq(0) @@ -23,21 +22,23 @@ class Analytics describe '#full?' do it 'returns true once item count is exceeded' do - 99.times { subject << Message.new(a: 'b') } + 99.times { subject << { a: 'b' } } expect(subject.full?).to be(false) - subject << Message.new(a: 'b') + subject << { a: 'b' } expect(subject.full?).to be(true) end it 'returns true once max size is almost exceeded' do - message = Message.new(a: 'b' * (Defaults::Message::MAX_BYTES - 10)) + message = { a: 'b' * (Defaults::Message::MAX_BYTES - 10) } + + message_size = message.to_json.bytesize # Each message is under the individual limit - expect(message.json_size).to be < Defaults::Message::MAX_BYTES + expect(message_size).to be < Defaults::Message::MAX_BYTES # Size of the batch is over the limit - expect(50 * message.json_size).to be > Defaults::MessageBatch::MAX_BYTES + expect(50 * message_size).to be > Defaults::MessageBatch::MAX_BYTES expect(subject.full?).to be(false) 50.times { subject << message } diff --git a/spec/segment/analytics/message_spec.rb b/spec/segment/analytics/message_spec.rb deleted file mode 100644 index b156183..0000000 --- a/spec/segment/analytics/message_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -module Segment - class Analytics - describe Message do - describe '#to_json' do - it 'caches JSON conversions' do - # Keeps track of the number of times to_json was called - nested_obj = Class.new do - attr_reader :to_json_call_count - - def initialize - @to_json_call_count = 0 - end - - def to_json(*_) - @to_json_call_count += 1 - '{}' - end - end.new - - message = Message.new('some_key' => nested_obj) - expect(nested_obj.to_json_call_count).to eq(0) - - message.to_json - expect(nested_obj.to_json_call_count).to eq(1) - - # When called a second time, the call count shouldn't increase - message.to_json - expect(nested_obj.to_json_call_count).to eq(1) - end - end - end - end -end From 220011d8f6daf4781bc75a2546c61481a6d182d3 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Jun 2018 18:32:30 +0530 Subject: [PATCH 208/322] Only install oj if ruby is > 2.0 and not jruby --- analytics-ruby.gemspec | 5 ++++- spec/isolated/with_active_support_and_oj.rb | 18 ++++++++++-------- spec/isolated/with_oj.rb | 14 ++++++++------ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 581f552..4edbfe9 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -24,7 +24,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'activesupport', '~> 4.1.11' spec.add_development_dependency 'faraday', '~> 0.13' spec.add_development_dependency 'pmap', '~> 1.1' - spec.add_development_dependency 'oj', '~> 3.6.2' + + if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' + spec.add_development_dependency 'oj', '~> 3.6.2' + end if RUBY_VERSION >= "2.1" spec.add_development_dependency 'rubocop', '~> 0.51.0' diff --git a/spec/isolated/with_active_support_and_oj.rb b/spec/isolated/with_active_support_and_oj.rb index 92d9a74..18724e3 100644 --- a/spec/isolated/with_active_support_and_oj.rb +++ b/spec/isolated/with_active_support_and_oj.rb @@ -1,14 +1,16 @@ require 'spec_helper' require 'isolated/json_example' -describe 'with active_support and oj' do - before do - require 'active_support' - require 'active_support/json' +if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' + describe 'with active_support and oj' do + before do + require 'active_support' + require 'active_support/json' - require 'oj' - Oj.mimic_JSON - end + require 'oj' + Oj.mimic_JSON + end - include_examples 'message_batch_json' + include_examples 'message_batch_json' + end end diff --git a/spec/isolated/with_oj.rb b/spec/isolated/with_oj.rb index 56b79ae..ad3f376 100644 --- a/spec/isolated/with_oj.rb +++ b/spec/isolated/with_oj.rb @@ -1,11 +1,13 @@ require 'spec_helper' require 'isolated/json_example' -describe 'with oj' do - before do - require 'oj' - Oj.mimic_JSON - end +if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' + describe 'with oj' do + before do + require 'oj' + Oj.mimic_JSON + end - include_examples 'message_batch_json' + include_examples 'message_batch_json' + end end From 494b3ff8ddf9b54df32bb75d08a948d79ab33de0 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 25 Jun 2018 13:48:34 +0530 Subject: [PATCH 209/322] Revert "Reuse TCP connections" Ref: https://github.com/segmentio/analytics-ruby/issues/167 This reverts commit 94179b8ae9b7686de462418b6a4f8c590d8902cf. --- lib/segment/analytics/request.rb | 5 ----- lib/segment/analytics/worker.rb | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index f1a319b..d65e86e 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -117,11 +117,6 @@ def send_request(write_key, batch) [200, '{}'] else - # If `start` is not called, Ruby adds a 'Connection: close' header to - # all requests, preventing us from reusing a connection for multiple - # HTTP requests - @http.start unless @http.started? - response = @http.request(request, payload) [response.code.to_i, response.body] end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index fd47703..eed6beb 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -30,7 +30,6 @@ def initialize(queue, write_key, options = {}) batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE @batch = MessageBatch.new(batch_size) @lock = Mutex.new - @request = Request.new end # public: Continuously runs the loop to check for new events @@ -45,7 +44,8 @@ def run end end - res = @request.post(@write_key, @batch) + res = Request.new.post @write_key, @batch + @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } From 4b9d328fc8f3989401a3df60ae0e068ea39fd4e5 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 27 Jun 2018 20:09:53 +0530 Subject: [PATCH 210/322] Prep for 2.2.6 release --- History.md | 12 ++++++++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2c38a4b..c7d3e91 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,15 @@ +2.2.6.pre / 2018-06-27 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/168): Revert 'reuse + TCP connections' to fix EMFILE errors + * [Fix](https://github.com/segmentio/analytics-ruby/pull/166): Fix oj/rails + conflict + * [Fix](https://github.com/segmentio/analytics-ruby/pull/162): Add missing + 'Forwardable' requirement + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/163): Better + logging + 2.2.5 / 2018-05-01 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 258a714..7d4028f 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.5' + VERSION = '2.2.6.pre' end end From b5c709daed449aef6a5a8dcecd851dd4e0707e3e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 27 Jun 2018 20:13:58 +0530 Subject: [PATCH 211/322] Disable e2e tests for a month They're failing because the runscope inspector shut down. --- spec/segment/analytics/e2e_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/segment/analytics/e2e_spec.rb b/spec/segment/analytics/e2e_spec.rb index e528d59..66a5b79 100644 --- a/spec/segment/analytics/e2e_spec.rb +++ b/spec/segment/analytics/e2e_spec.rb @@ -1,3 +1,5 @@ +require 'date' + require 'spec_helper' module Segment @@ -19,6 +21,10 @@ module Segment let(:runscope_client) { RunscopeClient.new(ENV.fetch('RUNSCOPE_TOKEN')) } it 'tracks events' do + # Runscope inspector has shut down, disable for a while until we've + # found a replacement. + skip if Date.today < Date.new(2018, 7, 27) + id = SecureRandom.uuid client.track( user_id: 'dummy_user_id', From f1d8b68ccd26b02ba382da8981caa1e28e9ebf8f Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 11 Jul 2018 00:13:25 +0530 Subject: [PATCH 212/322] Add note about oj/rails conflict to changelog --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index c7d3e91..bed1b48 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +Note: There is a known issue when using `2.2.5` with both `oj` and `rails` gems +installed. Please test out `2.2.6.pre` and hold off on using `2.2.5`. +[Details](https://github.com/segmentio/analytics-ruby/pull/166) + 2.2.6.pre / 2018-06-27 ================== From 60a1dfe425fbe2776cb3e498843bbf02b15bc1c0 Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Mon, 27 Aug 2018 14:06:36 -0700 Subject: [PATCH 213/322] Update end to end tests Use the testing harness that uses schema webhook instead of runscope. Other relevant changes: - Moved e2e tests from Travis.yml to Makefile - Refined list of files to include in gems, so that other files in directory don't cause installation problems (like .gem files) - Remove old e2e only code. --- .buildscript/e2e.sh | 13 +++++++ .travis.yml | 3 +- Makefile | 7 +++- Rakefile | 1 - analytics-ruby.gemspec | 4 +-- spec/helpers/runscope_client.rb | 38 --------------------- spec/segment/analytics/e2e_spec.rb | 54 ------------------------------ spec/spec_helper.rb | 1 - 8 files changed, 22 insertions(+), 99 deletions(-) create mode 100755 .buildscript/e2e.sh delete mode 100644 spec/helpers/runscope_client.rb delete mode 100644 spec/segment/analytics/e2e_spec.rb diff --git a/.buildscript/e2e.sh b/.buildscript/e2e.sh new file mode 100755 index 0000000..ed25afe --- /dev/null +++ b/.buildscript/e2e.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -ex + +if [ "$RUN_E2E_TESTS" != "true" ]; then + echo "Skipping end to end tests." +else + echo "Running end to end tests..." + wget https://github.com/segmentio/library-e2e-tester/releases/download/0.2.1/tester_linux_amd64 -O tester + chmod +x tester + ./tester -path='./bin/analytics' + echo "End to end tests completed!" +fi diff --git a/.travis.yml b/.travis.yml index d6fc9ca..5cc960c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ rvm: - 2.4.3 - 2.5.0 # Performs deploys. Change condition below when changing this. -script: make check +script: + - make check # Deploy tagged commits to Rubygems # See https://docs.travis-ci.com/user/deployment/rubygems/ for more details diff --git a/Makefile b/Makefile index a027075..44b6c43 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,14 @@ dependencies: bundle install --verbose # Run all tests and checks (including linters). -check: +check: install # Installation required for testing binary bundle exec rake + sh .buildscript/e2e.sh # Compile the code and produce any binaries where applicable. build: + rm -f analytics-ruby-*.gem gem build ./analytics-ruby.gemspec + +install: build + gem install analytics-ruby-*.gem diff --git a/Rakefile b/Rakefile index 838c39f..0d3f11a 100644 --- a/Rakefile +++ b/Rakefile @@ -4,7 +4,6 @@ default_tasks = [] RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/segment/**/*_spec.rb' - spec.rspec_opts = "--tag ~e2e" if ENV["RUN_E2E_TESTS"] != "true" end default_tasks << :spec diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 4edbfe9..e1934c6 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -3,7 +3,7 @@ require File.expand_path('../lib/segment/analytics/version', __FILE__) Gem::Specification.new do |spec| spec.name = 'analytics-ruby' spec.version = Segment::Analytics::VERSION - spec.files = Dir.glob('**/*') + spec.files = Dir.glob("{lib,bin}/**/*") spec.require_paths = ['lib'] spec.bindir = 'bin' spec.executables = ['analytics'] @@ -22,8 +22,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '~> 4.1.11' - spec.add_development_dependency 'faraday', '~> 0.13' - spec.add_development_dependency 'pmap', '~> 1.1' if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' spec.add_development_dependency 'oj', '~> 3.6.2' diff --git a/spec/helpers/runscope_client.rb b/spec/helpers/runscope_client.rb deleted file mode 100644 index a68be26..0000000 --- a/spec/helpers/runscope_client.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'faraday' -require 'pmap' - -class RunscopeClient - def initialize(api_token) - headers = { 'Authorization' => "Bearer #{api_token}" } - @conn = Faraday.new('https://api.runscope.com', headers: headers) - end - - def requests(bucket_key) - with_retries(3) do - response = @conn.get("/buckets/#{bucket_key}/messages", count: 20) - - raise "Runscope error. #{response.body}" unless response.status == 200 - - message_uuids = JSON.parse(response.body)['data'].map { |message| - message.fetch('uuid') - } - - message_uuids.pmap { |uuid| - response = @conn.get("/buckets/#{bucket_key}/messages/#{uuid}") - raise "Runscope error. #{response.body}" unless response.status == 200 - JSON.parse(response.body).fetch('data').fetch('request') - } - end - end - - private - - def with_retries(max_retries) - retries ||= 0 - yield - rescue StandardError => e - retries += 1 - retry if retries < max_retries - raise e - end -end diff --git a/spec/segment/analytics/e2e_spec.rb b/spec/segment/analytics/e2e_spec.rb deleted file mode 100644 index 66a5b79..0000000 --- a/spec/segment/analytics/e2e_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'date' - -require 'spec_helper' - -module Segment - # End-to-end tests that send events to a segment source and verifies that a - # webhook connected to the source (configured manually via the app) is able - # to receive the data sent by this library. - describe 'End-to-end tests', e2e: true do - # Segment write key for - # https://app.segment.com/segment-libraries/sources/analytics_ruby_e2e_test/overview. - # - # This source is configured to send events to the Runscope bucket used by - # this test. - WRITE_KEY = 'qhdMksLsQTi9MES3CHyzsWRRt4ub5VM6' - - # Runscope bucket key for https://www.runscope.com/stream/umkvkgv7ndby - RUNSCOPE_BUCKET_KEY = 'umkvkgv7ndby' - - let(:client) { Segment::Analytics.new(write_key: WRITE_KEY) } - let(:runscope_client) { RunscopeClient.new(ENV.fetch('RUNSCOPE_TOKEN')) } - - it 'tracks events' do - # Runscope inspector has shut down, disable for a while until we've - # found a replacement. - skip if Date.today < Date.new(2018, 7, 27) - - id = SecureRandom.uuid - client.track( - user_id: 'dummy_user_id', - event: 'E2E Test', - properties: { id: id } - ) - client.flush - - # Allow events to propagate to runscope - eventually(timeout: 30) { - expect(has_matching_request?(id)).to eq(true) - } - end - - def has_matching_request?(id) - captured_requests = runscope_client.requests(RUNSCOPE_BUCKET_KEY) - captured_requests.any? do |request| - begin - body = JSON.parse(request['body']) - body['properties'] && body['properties']['id'] == id - rescue JSON::ParserError - false - end - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7cd6c2f..8bc41b0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,7 +6,6 @@ require 'segment/analytics' require 'active_support/time' -require './spec/helpers/runscope_client' # Setting timezone for ActiveSupport::TimeWithZone to UTC Time.zone = 'UTC' From 8837903a3397db9404db4a28cfd6909aaf7e0282 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 20 Oct 2018 12:08:03 +0530 Subject: [PATCH 214/322] Move commander to development_dependencies, fixes #176 --- analytics-ruby.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index e1934c6..ba0c3fe 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -16,20 +16,20 @@ Gem::Specification.new do |spec| # Ruby 1.8 requires json spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" - spec.add_dependency 'commander', '~> 4.4' + # Used in the executable testing script + spec.add_development_dependency 'commander', '~> 4.4' + + # Used in specs spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'tzinfo', '1.2.1' spec.add_development_dependency 'activesupport', '~> 4.1.11' - if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' spec.add_development_dependency 'oj', '~> 3.6.2' end - - if RUBY_VERSION >= "2.1" + if RUBY_VERSION >= '2.1' spec.add_development_dependency 'rubocop', '~> 0.51.0' end - spec.add_development_dependency 'codecov', '~> 0.1.4' end From ddf7a20b6b793b1f632a3610af6b42d066b0376f Mon Sep 17 00:00:00 2001 From: William Grosset Date: Thu, 28 Feb 2019 20:47:16 -0800 Subject: [PATCH 215/322] Update README --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index e611131..abfd092 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,48 @@ analytics-ruby analytics-ruby is a ruby client for [Segment](https://segment.com) +
+ +

You can't fix what you can't measure

+
+ +Analytics helps you measure your users, product, and business. It unlocks insights into your app's funnel, core business metrics, and whether you have product-market fit. + +## How to get started +1. **Collect analytics data** from your app(s). + - The top 200 Segment companies collect data from 5+ source types (web, mobile, server, CRM, etc.). +2. **Send the data to analytics tools** (for example, Google Analytics, Amplitude, Mixpanel). + - Over 250+ Segment companies send data to eight categories of destinations such as analytics tools, warehouses, email marketing and remarketing systems, session recording, and more. +3. **Explore your data** by creating metrics (for example, new signups, retention cohorts, and revenue generation). + - The best Segment companies use retention cohorts to measure product market fit. Netflix has 70% paid retention after 12 months, 30% after 7 years. + +[Segment](https://segment.com) collects analytics data and allows you to send it to more than 250 apps (such as Google Analytics, Mixpanel, Optimizely, Facebook Ads, Slack, Sentry) just by flipping a switch. You only need one Segment code snippet, and you can turn integrations on and off at will, with no additional code. [Sign up with Segment today](https://app.segment.com/signup). + +### Why? +1. **Power all your analytics apps with the same data**. Instead of writing code to integrate all of your tools individually, send data to Segment, once. + +2. **Install tracking for the last time**. We're the last integration you'll ever need to write. You only need to instrument Segment once. Reduce all of your tracking code and advertising tags into a single set of API calls. + +3. **Send data from anywhere**. Send Segment data from any device, and we'll transform and send it on to any tool. + +4. **Query your data in SQL**. Slice, dice, and analyze your data in detail with Segment SQL. We'll transform and load your customer behavioral data directly from your apps into Amazon Redshift, Google BigQuery, or Postgres. Save weeks of engineering time by not having to invent your own data warehouse and ETL pipeline. + + For example, you can capture data on any app: + ```js + analytics.track('Order Completed', { price: 99.84 }) + ``` + Then, query the resulting data in SQL: + ```sql + select * from app.order_completed + order by price desc + ``` + +### 🚀 Startup Program +
+ +
+If you are part of a new startup (<$5M raised, <2 years since founding), we just launched a new startup program for you. You can get a Segment Team plan (up to $25,000 value in Segment credits) for free up to 2 years — apply here! + ## Install Into Gemfile from rubygems.org: From 3b34da1af7430ae703aa1b687c7e30b0b8e1ffcf Mon Sep 17 00:00:00 2001 From: William Grosset Date: Sun, 3 Mar 2019 19:24:31 -0800 Subject: [PATCH 216/322] Remove build status from README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index abfd092..2c44245 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ analytics-ruby ============== -[![Build Status](https://travis-ci.org/segmentio/analytics-ruby.png?branch=master)](https://travis-ci.org/segmentio/analytics-ruby) - analytics-ruby is a ruby client for [Segment](https://segment.com)
From 7d42b0dc048db5955b5e33ddb0afe1cebb7c4a73 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 14:27:46 +0530 Subject: [PATCH 217/322] Refine DateTime tests to use iso8601 helpers --- spec/segment/analytics/client_spec.rb | 47 +++++++++++++++++---------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index f8a52e3..178eb13 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -76,25 +76,30 @@ class Analytics end.to_not raise_error end - it 'converts time and date traits into iso8601 format' do + it 'converts time and date properties into iso8601 format' do client.track({ :user_id => 'user', :event => 'Event', :properties => { :time => Time.utc(2013), - :time_with_zone => Time.zone.parse('2013-01-01'), + :time_with_zone => Time.zone.parse('2013-01-01'), :date_time => DateTime.new(2013, 1, 1), :date => Date.new(2013, 1, 1), :nottime => 'x' } }) - message = queue.pop + message = queue.pop properties = message[:properties] - expect(properties[:time]).to eq('2013-01-01T00:00:00.000Z') - expect(properties[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(properties[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') - expect(properties[:date]).to eq('2013-01-01') + + date_time = DateTime.new(2013, 1, 1) + expect(Time.iso8601(properties[:time])).to eq(date_time) + expect(Time.iso8601(properties[:time_with_zone])).to eq(date_time) + expect(Time.iso8601(properties[:date_time])).to eq(date_time) + + date = Date.new(2013, 1, 1) + expect(Date.iso8601(properties[:date])).to eq(date) + expect(properties[:nottime]).to eq('x') end end @@ -131,12 +136,16 @@ class Analytics }) message = queue.pop - traits = message[:traits] - expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z') - expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') - expect(traits[:date]).to eq('2013-01-01') + + date_time = DateTime.new(2013, 1, 1) + expect(Time.iso8601(traits[:time])).to eq(date_time) + expect(Time.iso8601(traits[:time_with_zone])).to eq(date_time) + expect(Time.iso8601(traits[:date_time])).to eq(date_time) + + date = Date.new(2013, 1, 1) + expect(Date.iso8601(traits[:date])).to eq(date) + expect(traits[:nottime]).to eq('x') end end @@ -192,12 +201,16 @@ class Analytics }) message = queue.pop - traits = message[:traits] - expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z') - expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z') - expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00') - expect(traits[:date]).to eq('2013-01-01') + + date_time = DateTime.new(2013, 1, 1) + expect(Time.iso8601(traits[:time])).to eq(date_time) + expect(Time.iso8601(traits[:time_with_zone])).to eq(date_time) + expect(Time.iso8601(traits[:date_time])).to eq(date_time) + + date = Date.new(2013, 1, 1) + expect(Date.iso8601(traits[:date])).to eq(date) + expect(traits[:nottime]).to eq('x') end end From 070fefcf7be6f32a4ed086db98488c97bb750092 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 14:57:46 +0530 Subject: [PATCH 218/322] Remove #sleep calls in tests --- spec/segment/analytics_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index bc06703..fcf5f51 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -17,7 +17,7 @@ class Analytics it 'does not error with the required options' do expect do analytics.track Queued::TRACK - sleep(1) + analytics.flush end.to_not raise_error end end @@ -29,7 +29,7 @@ class Analytics it 'does not error with the required options' do analytics.identify Queued::IDENTIFY - sleep(1) + analytics.flush end end @@ -45,7 +45,7 @@ class Analytics it 'does not error with the required options' do expect do analytics.alias ALIAS - sleep(1) + analytics.flush end.to_not raise_error end end @@ -62,7 +62,7 @@ class Analytics it 'does not error with the required options' do expect do analytics.group Queued::GROUP - sleep(1) + analytics.flush end.to_not raise_error end end @@ -75,7 +75,7 @@ class Analytics it 'does not error with the required options' do expect do analytics.page Queued::PAGE - sleep(1) + analytics.flush end.to_not raise_error end end @@ -88,7 +88,7 @@ class Analytics it 'does not error with the required options' do expect do analytics.screen Queued::SCREEN - sleep(1) + analytics.flush end.to_not raise_error end end From e55cd8713b95df99a440f2efc20385881a8398e0 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 15:12:45 +0530 Subject: [PATCH 219/322] Don't assume that all errors are ConnectionErrors --- lib/segment/analytics/request.rb | 2 +- spec/segment/analytics/request_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index d65e86e..5fcb1c2 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -53,7 +53,7 @@ def post(write_key, batch) if exception logger.error(exception.message) exception.backtrace.each { |line| logger.error(line) } - Response.new(-1, "Connection error: #{exception}") + Response.new(-1, exception.to_s) else last_response end diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/request_spec.rb index 721d7e9..b4d0f63 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/request_spec.rb @@ -232,7 +232,7 @@ class Analytics it 'has a connection error' do error = subject.post(write_key, batch).error - expect(error).to match(/Connection error/) + expect(error).to match(/Malformed JSON/) end it_behaves_like('retried request', 200, 'Malformed JSON ---') From d07c42b0a4e0ea66e2bd1adbc665f92fdc386588 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 11 Jul 2018 12:21:12 +0530 Subject: [PATCH 220/322] Promote 2.2.6.pre to 2.2.6 --- History.md | 9 ++++++--- lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/History.md b/History.md index bed1b48..c776a65 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,9 @@ -Note: There is a known issue when using `2.2.5` with both `oj` and `rails` gems -installed. Please test out `2.2.6.pre` and hold off on using `2.2.5`. -[Details](https://github.com/segmentio/analytics-ruby/pull/166) +2.2.6 / 2018-06-11 +================== + + * Promote pre-release version to stable. + * [Fix](https://github.com/segmentio/analytics-ruby/pull/187): Don't assume + all errors are 'ConnectionError's 2.2.6.pre / 2018-06-27 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 7d4028f..70aa0a5 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.6.pre' + VERSION = '2.2.6' end end From 7681bdcae983b4347e1a0efff1288f3a93793ec6 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 15:36:50 +0530 Subject: [PATCH 221/322] Remove unused method --- lib/segment/analytics/client.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index ce830a3..d41a357 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -388,19 +388,6 @@ def check_timestamp!(timestamp) raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time end - def event(attrs) - symbolize_keys! attrs - - { - :userId => user_id, - :name => name, - :properties => properties, - :context => context, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'screen' - } - end - def check_user_id!(attrs) unless attrs[:user_id] || attrs[:anonymous_id] raise ArgumentError, 'Must supply either user_id or anonymous_id' From cb00e85efcf27af378b0902dfe03a448adc3a8df Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 15:39:55 +0530 Subject: [PATCH 222/322] Remove unused instance variable --- lib/segment/analytics/client.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index d41a357..5451bd6 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -23,9 +23,8 @@ def initialize(opts = {}) @queue = Queue.new @write_key = opts[:write_key] @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE - @options = opts @worker_mutex = Mutex.new - @worker = Worker.new(@queue, @write_key, @options) + @worker = Worker.new(@queue, @write_key, opts) check_write_key! From 66d941a82e7a7e84b36285a3e2e0f84f1e290d7c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 16:18:46 +0530 Subject: [PATCH 223/322] Add FieldParser, use in #track --- lib/segment/analytics.rb | 1 + lib/segment/analytics/client.rb | 44 +++----------- lib/segment/analytics/field_parser.rb | 85 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 lib/segment/analytics/field_parser.rb diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index c03f8cc..d6f1df8 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -1,6 +1,7 @@ require 'segment/analytics/version' require 'segment/analytics/defaults' require 'segment/analytics/utils' +require 'segment/analytics/field_parser' require 'segment/analytics/client' require 'segment/analytics/worker' require 'segment/analytics/request' diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 5451bd6..6547b3c 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -47,53 +47,25 @@ def flush # @see https://segment.com/docs/sources/server/ruby/#track # # @param [Hash] attrs + # + # @option attrs [String] :event Event name + # @option attrs [Hash] :properties Event properties (optional) + # # @option attrs [String] :anonymous_id ID for a user when you don't know # who they are yet. (optional but you must provide either an # `anonymous_id` or `user_id`) # @option attrs [Hash] :context ({}) - # @option attrs [String] :event Event name # @option attrs [Hash] :integrations What integrations this event # goes to (optional) - # @option attrs [Hash] :options Options such as user traits (optional) - # @option attrs [Hash] :properties Event properties (optional) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) # @option attrs [Time] :timestamp When the event occurred (optional) # @option attrs [String] :user_id The ID for this user in your database # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) + # @option attrs [Hash] :options Options such as user traits (optional) def track(attrs) symbolize_keys! attrs - check_user_id! attrs - - event = attrs[:event] - properties = attrs[:properties] || {} - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - check_timestamp! timestamp - - if event.nil? || event.empty? - raise ArgumentError, 'Must supply event as a non-empty string' - end - - raise ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash - isoify_dates! properties - - add_context context - - enqueue({ - :event => event, - :userId => attrs[:user_id], - :anonymousId => attrs[:anonymous_id], - :context => context, - :options => attrs[:options], - :integrations => attrs[:integrations], - :properties => properties, - :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'track' - }) + enqueue(FieldParser.parse_for_track(attrs)) end # Identifies a user diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb new file mode 100644 index 0000000..157f34a --- /dev/null +++ b/lib/segment/analytics/field_parser.rb @@ -0,0 +1,85 @@ +module Segment + class Analytics + # Handles parsing fields according to the Segment Spec + # + # @see https://segment.com/docs/spec/ + class FieldParser + class << self + include Segment::Analytics::Utils + + # In addition to the common fields, track accepts: + # + # - "event" + # - "properties" + def parse_for_track(fields) + common = parse_common_fields(fields) + + event = fields[:event] + properties = fields[:properties] || {} + + check_presence!(event, 'event') + check_is_hash!(properties, 'properties') + + isoify_dates! properties + + common.merge({ + :type => 'track', + :event => event.to_s, + :properties => properties + }) + end + + private + + def parse_common_fields(fields) + timestamp = fields[:timestamp] || Time.new + message_id = fields[:message_id].to_s if fields[:message_id] + context = fields[:context] || {} + + check_user_id! fields + check_timestamp! timestamp + + add_context! context + + { + :anonymousId => fields[:anonymous_id], + :context => context, + :integrations => fields[:integrations], + :messageId => message_id, + :timestamp => datetime_in_iso8601(timestamp), + :userId => fields[:user_id], + :options => fields[:options] # Not in spec, retained for backward compatibility + } + end + + def check_user_id!(fields) + unless fields[:user_id] || fields[:anonymous_id] + raise ArgumentError, 'Must supply either user_id or anonymous_id' + end + end + + def check_timestamp!(timestamp) + raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time + end + + def add_context!(context) + context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s } + end + + # private: Ensures that a string is non-empty + # + # obj - String|Number that must be non-blank + # name - Name of the validated value + def check_presence!(obj, name) + if obj.nil? || (obj.is_a?(String) && obj.empty?) + raise ArgumentError, "#{name} must be given" + end + end + + def check_is_hash!(obj, name) + raise ArgumentError, "#{name} must be a Hash" unless obj.is_a? Hash + end + end + end + end +end From 62f4a24669e312817769599dfb5ca08405a93305 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 16:36:55 +0530 Subject: [PATCH 224/322] Use common fields parser for #identify --- lib/segment/analytics/client.rb | 36 ++++++--------------------- lib/segment/analytics/field_parser.rb | 16 ++++++++++++ 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 6547b3c..6aea723 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -73,46 +73,24 @@ def track(attrs) # @see https://segment.com/docs/sources/server/ruby/#identify # # @param [Hash] attrs + # + # @option attrs [Hash] :traits User traits (optional) + # # @option attrs [String] :anonymous_id ID for a user when you don't know # who they are yet. (optional but you must provide either an # `anonymous_id` or `user_id`) # @option attrs [Hash] :context ({}) # @option attrs [Hash] :integrations What integrations this event # goes to (optional) - # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [Hash] :traits User traits (optional) # @option attrs [String] :user_id The ID for this user in your database # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [String] :message_id ID that uniquely identifies a - # message across the API. (optional) + # @option attrs [Hash] :options Options such as user traits (optional) def identify(attrs) symbolize_keys! attrs - check_user_id! attrs - - traits = attrs[:traits] || {} - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - check_timestamp! timestamp - - raise ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash - isoify_dates! traits - - add_context context - - enqueue({ - :userId => attrs[:user_id], - :anonymousId => attrs[:anonymous_id], - :integrations => attrs[:integrations], - :context => context, - :traits => traits, - :options => attrs[:options], - :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'identify' - }) + enqueue(FieldParser.parse_for_identify(attrs)) end # Aliases a user from one id to another diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 157f34a..c7a8532 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -29,6 +29,22 @@ def parse_for_track(fields) }) end + # In addition to the common fields, identify accepts: + # + # - "traits" + def parse_for_identify(fields) + common = parse_common_fields(fields) + + traits = fields[:traits] || {} + check_is_hash!(traits, 'traits') + isoify_dates! traits + + common.merge({ + :type => 'identify', + :traits => traits + }) + end + private def parse_common_fields(fields) From 15369fe25cdf74fceee465bd758ca53799e283ea Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 16:47:34 +0530 Subject: [PATCH 225/322] Use common fields parser for #alias --- lib/segment/analytics/client.rb | 43 +++++++++------------------ lib/segment/analytics/field_parser.rb | 15 ++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 6aea723..8937fd1 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -98,39 +98,24 @@ def identify(attrs) # @see https://segment.com/docs/sources/server/ruby/#alias # # @param [Hash] attrs + # + # @option attrs [String] :previous_id The ID to alias from + # + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this must be - # sent to (optional) + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) # @option attrs [Hash] :options Options such as user traits (optional) - # @option attrs [String] :previous_id The ID to alias from - # @option attrs [Time] :timestamp When the alias occurred (optional) - # @option attrs [String] :user_id The ID to alias to - # @option attrs [String] :message_id ID that uniquely identifies a - # message across the API. (optional) def alias(attrs) symbolize_keys! attrs - - from = attrs[:previous_id] - to = attrs[:user_id] - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - check_presence! from, 'previous_id' - check_presence! to, 'user_id' - check_timestamp! timestamp - add_context context - - enqueue({ - :previousId => from, - :userId => to, - :integrations => attrs[:integrations], - :context => context, - :options => attrs[:options], - :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'alias' - }) + enqueue(FieldParser.parse_for_alias(attrs)) end # Associates a user identity with a group. diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index c7a8532..ef77f79 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -45,6 +45,21 @@ def parse_for_identify(fields) }) end + # In addition to the common fields, alias accepts: + # + # - "previous_id" + def parse_for_alias(fields) + common = parse_common_fields(fields) + + previous_id = fields[:previous_id] + check_presence!(previous_id, 'previous_id') + + common.merge({ + :type => 'alias', + :previousId => previous_id + }) + end + private def parse_common_fields(fields) From fd8faa9ad3ee2c01503323a94f8e47124d139ba5 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 16:55:04 +0530 Subject: [PATCH 226/322] Use common fields parser for #group --- lib/segment/analytics/client.rb | 54 +++++---------------------- lib/segment/analytics/field_parser.rb | 22 +++++++++++ 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 8937fd1..8357ccf 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -123,48 +123,25 @@ def alias(attrs) # @see https://segment.com/docs/sources/server/ruby/#group # # @param [Hash] attrs + # + # @option attrs [String] :group_id The ID of the group + # @option attrs [Hash] :traits User traits (optional) + # # @option attrs [String] :anonymous_id ID for a user when you don't know # who they are yet. (optional but you must provide either an # `anonymous_id` or `user_id`) # @option attrs [Hash] :context ({}) - # @option attrs [String] :group_id The ID of the group # @option attrs [Hash] :integrations What integrations this event # goes to (optional) - # @option attrs [Hash] :options Options such as user traits (optional) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for the user that is part of - # the group - # @option attrs [String] :message_id ID that uniquely identifies a - # message across the API. (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) + # @option attrs [Hash] :options Options such as user traits (optional) def group(attrs) symbolize_keys! attrs - check_user_id! attrs - - group_id = attrs[:group_id] - user_id = attrs[:user_id] - traits = attrs[:traits] || {} - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - raise ArgumentError, '.traits must be a hash' unless traits.is_a? Hash - isoify_dates! traits - - check_presence! group_id, 'group_id' - check_timestamp! timestamp - add_context context - - enqueue({ - :groupId => group_id, - :userId => user_id, - :traits => traits, - :integrations => attrs[:integrations], - :options => attrs[:options], - :context => context, - :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'group' - }) + enqueue(FieldParser.parse_for_group(attrs)) end # Records a page view @@ -294,17 +271,6 @@ def enqueue(action) end end - # private: Ensures that a string is non-empty - # - # obj - String|Number that must be non-blank - # name - Name of the validated value - # - def check_presence!(obj, name) - if obj.nil? || (obj.is_a?(String) && obj.empty?) - raise ArgumentError, "#{name} must be given" - end - end - # private: Adds contextual information to the call # # context - Hash of call context diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index ef77f79..f3ce094 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -60,6 +60,28 @@ def parse_for_alias(fields) }) end + # In addition to the common fields, group accepts: + # + # - "group_id" + # - "traits" + def parse_for_group(fields) + common = parse_common_fields(fields) + + group_id = fields[:group_id] + traits = fields[:traits] || {} + + check_presence!(group_id, 'group_id') + check_is_hash!(traits, 'traits') + + isoify_dates! traits + + common.merge({ + :type => 'group', + :groupId => group_id, + :traits => traits + }) + end + private def parse_common_fields(fields) From ecc5576c361665ab1d2c59e641c7ee832772eab5 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 17:08:13 +0530 Subject: [PATCH 227/322] Use common field parser for #page --- lib/segment/analytics/client.rb | 44 ++++++--------------------- lib/segment/analytics/field_parser.rb | 22 ++++++++++++++ 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 8357ccf..07c66f6 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -149,49 +149,25 @@ def group(attrs) # @see https://segment.com/docs/sources/server/ruby/#page # # @param [Hash] attrs + # + # @option attrs [String] :name Name of the page + # @option attrs [Hash] :properties Page properties (optional) + # # @option attrs [String] :anonymous_id ID for a user when you don't know # who they are yet. (optional but you must provide either an # `anonymous_id` or `user_id`) - # @option attrs [String] :category The page category (optional) # @option attrs [Hash] :context ({}) # @option attrs [Hash] :integrations What integrations this event # goes to (optional) - # @option attrs [String] :name Name of the page + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) # @option attrs [Hash] :options Options such as user traits (optional) - # @option attrs [Hash] :properties Page properties (optional) - # @option attrs [Time] :timestamp When the pageview occurred (optional) - # @option attrs [String] :user_id The ID of the user viewing the page - # @option attrs [String] :message_id ID that uniquely identifies a - # message across the API. (optional) def page(attrs) symbolize_keys! attrs - check_user_id! attrs - - name = attrs[:name].to_s - properties = attrs[:properties] || {} - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash - isoify_dates! properties - - check_timestamp! timestamp - add_context context - - enqueue({ - :userId => attrs[:user_id], - :anonymousId => attrs[:anonymous_id], - :name => name, - :category => attrs[:category], - :properties => properties, - :integrations => attrs[:integrations], - :options => attrs[:options], - :context => context, - :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :type => 'page' - }) + enqueue(FieldParser.parse_for_page(attrs)) end # Records a screen view (for a mobile app) diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index f3ce094..a45526d 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -82,6 +82,28 @@ def parse_for_group(fields) }) end + # In addition to the common fields, page accepts: + # + # - "name" + # - "properties" + def parse_for_page(fields) + common = parse_common_fields(fields) + + name = fields[:name] + properties = fields[:properties] || {} + + check_presence!(name, 'name') + check_is_hash!(properties, 'properties') + + isoify_dates! properties + + common.merge({ + :type => 'page', + :name => name.to_s, + :properties => properties + }) + end + private def parse_common_fields(fields) From 84e5eb7c586d6b5cfb89686bb6324e1240a4d7b4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 17:13:41 +0530 Subject: [PATCH 228/322] Use common field parser for #screen --- lib/segment/analytics/client.rb | 45 +++++++-------------------- lib/segment/analytics/field_parser.rb | 25 +++++++++++++++ 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 07c66f6..2a8f509 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -173,49 +173,26 @@ def page(attrs) # Records a screen view (for a mobile app) # # @param [Hash] attrs + # + # @option attrs [String] :name Name of the screen + # @option attrs [Hash] :properties Screen properties (optional) + # @option attrs [String] :category The screen category (optional) + # # @option attrs [String] :anonymous_id ID for a user when you don't know # who they are yet. (optional but you must provide either an # `anonymous_id` or `user_id`) - # @option attrs [String] :category The screen category (optional) # @option attrs [Hash] :context ({}) # @option attrs [Hash] :integrations What integrations this event # goes to (optional) - # @option attrs [String] :name Name of the screen + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) # @option attrs [Hash] :options Options such as user traits (optional) - # @option attrs [Hash] :properties Page properties (optional) - # @option attrs [Time] :timestamp When the pageview occurred (optional) - # @option attrs [String] :user_id The ID of the user viewing the screen - # @option attrs [String] :message_id ID that uniquely identifies a - # message across the API. (optional) def screen(attrs) symbolize_keys! attrs - check_user_id! attrs - - name = attrs[:name].to_s - properties = attrs[:properties] || {} - timestamp = attrs[:timestamp] || Time.new - context = attrs[:context] || {} - message_id = attrs[:message_id].to_s if attrs[:message_id] - - raise ArgumentError, '.properties must be a hash' unless properties.is_a? Hash - isoify_dates! properties - - check_timestamp! timestamp - add_context context - - enqueue({ - :userId => attrs[:user_id], - :anonymousId => attrs[:anonymous_id], - :name => name, - :properties => properties, - :category => attrs[:category], - :options => attrs[:options], - :integrations => attrs[:integrations], - :context => context, - :messageId => message_id, - :timestamp => timestamp.iso8601, - :type => 'screen' - }) + enqueue(FieldParser.parse_for_screen(attrs)) end # @return [Fixnum] number of messages in the queue diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index a45526d..4f142f7 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -104,6 +104,31 @@ def parse_for_page(fields) }) end + # In addition to the common fields, screen accepts: + # + # - "name" + # - "properties" + # - "category" (Not in spec, retained for backward compatibility" + def parse_for_screen(fields) + common = parse_common_fields(fields) + + name = fields[:name] + properties = fields[:properties] || {} + category = fields[:category] + + check_presence!(name, 'name') + check_is_hash!(properties, 'properties') + + isoify_dates! properties + + common.merge({ + :type => 'screen', + :name => name, + :properties => properties, + :category => category + }) + end + private def parse_common_fields(fields) From 15d957ae84fbae9409bf7ce3dd805efd37aa087c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Mar 2019 17:17:49 +0530 Subject: [PATCH 229/322] Remove unused private methods --- lib/segment/analytics/client.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 2a8f509..e75e0fb 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -224,29 +224,11 @@ def enqueue(action) end end - # private: Adds contextual information to the call - # - # context - Hash of call context - def add_context(context) - context[:library] = { :name => 'analytics-ruby', :version => Segment::Analytics::VERSION.to_s } - end - # private: Checks that the write_key is properly initialized def check_write_key! raise ArgumentError, 'Write key must be initialized' if @write_key.nil? end - # private: Checks the timstamp option to make sure it is a Time. - def check_timestamp!(timestamp) - raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time - end - - def check_user_id!(attrs) - unless attrs[:user_id] || attrs[:anonymous_id] - raise ArgumentError, 'Must supply either user_id or anonymous_id' - end - end - def ensure_worker_running return if worker_running? @worker_mutex.synchronize do From 153bc4665d498ed122c2a4ed5bfdcf8045c34efc Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 6 Apr 2019 14:15:39 +0530 Subject: [PATCH 230/322] Simplify repetitive documentation using macros --- lib/segment/analytics/client.rb | 98 +++++++-------------------------- 1 file changed, 20 insertions(+), 78 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index e75e0fb..e83c8d8 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -42,6 +42,20 @@ def flush end end + # @!macro common_attrs + # @option attrs [String] :anonymous_id ID for a user when you don't know + # who they are yet. (optional but you must provide either an + # `anonymous_id` or `user_id`) + # @option attrs [Hash] :context ({}) + # @option attrs [Hash] :integrations What integrations this event + # goes to (optional) + # @option attrs [String] :message_id ID that uniquely + # identifies a message across the API. (optional) + # @option attrs [Time] :timestamp When the event occurred (optional) + # @option attrs [String] :user_id The ID for this user in your database + # (optional but you must provide either an `anonymous_id` or `user_id`) + # @option attrs [Hash] :options Options such as user traits (optional) + # Tracks an event # # @see https://segment.com/docs/sources/server/ruby/#track @@ -50,19 +64,7 @@ def flush # # @option attrs [String] :event Event name # @option attrs [Hash] :properties Event properties (optional) - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def track(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_track(attrs)) @@ -75,19 +77,7 @@ def track(attrs) # @param [Hash] attrs # # @option attrs [Hash] :traits User traits (optional) - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def identify(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_identify(attrs)) @@ -100,19 +90,7 @@ def identify(attrs) # @param [Hash] attrs # # @option attrs [String] :previous_id The ID to alias from - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def alias(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_alias(attrs)) @@ -126,19 +104,7 @@ def alias(attrs) # # @option attrs [String] :group_id The ID of the group # @option attrs [Hash] :traits User traits (optional) - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def group(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_group(attrs)) @@ -152,19 +118,7 @@ def group(attrs) # # @option attrs [String] :name Name of the page # @option attrs [Hash] :properties Page properties (optional) - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def page(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_page(attrs)) @@ -177,19 +131,7 @@ def page(attrs) # @option attrs [String] :name Name of the screen # @option attrs [Hash] :properties Screen properties (optional) # @option attrs [String] :category The screen category (optional) - # - # @option attrs [String] :anonymous_id ID for a user when you don't know - # who they are yet. (optional but you must provide either an - # `anonymous_id` or `user_id`) - # @option attrs [Hash] :context ({}) - # @option attrs [Hash] :integrations What integrations this event - # goes to (optional) - # @option attrs [String] :message_id ID that uniquely - # identifies a message across the API. (optional) - # @option attrs [Time] :timestamp When the event occurred (optional) - # @option attrs [String] :user_id The ID for this user in your database - # (optional but you must provide either an `anonymous_id` or `user_id`) - # @option attrs [Hash] :options Options such as user traits (optional) + # @macro common_attrs def screen(attrs) symbolize_keys! attrs enqueue(FieldParser.parse_for_screen(attrs)) From e2336c24391f92102d3481bf43db12137fd40d0e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 6 Apr 2019 14:36:33 +0530 Subject: [PATCH 231/322] Add extra tests for user_id/anonymous_id --- spec/segment/analytics_spec.rb | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index fcf5f51..f4444dd 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -10,8 +10,10 @@ class Analytics expect { analytics.track(:user_id => 'user') }.to raise_error(ArgumentError) end - it 'errors without a user_id' do - expect { analytics.track(:event => 'Event') }.to raise_error(ArgumentError) + it 'errors without user_id or anonymous_id' do + expect { analytics.track :event => 'event' }.to raise_error(ArgumentError) + expect { analytics.track :event => 'event', user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.track :event => 'event', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do @@ -23,8 +25,10 @@ class Analytics end describe '#identify' do - it 'errors without a user_id' do + it 'errors without user_id or anonymous_id' do expect { analytics.identify :traits => {} }.to raise_error(ArgumentError) + expect { analytics.identify :traits => {}, user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.identify :traits => {}, anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do @@ -34,12 +38,14 @@ class Analytics end describe '#alias' do - it 'errors without from' do + it 'errors without previous_id' do expect { analytics.alias :user_id => 1234 }.to raise_error(ArgumentError) end - it 'errors without to' do - expect { analytics.alias :previous_id => 1234 }.to raise_error(ArgumentError) + it 'errors without user_id or anonymous_id' do + expect { analytics.alias :previous_id => 'foo' }.to raise_error(ArgumentError) + expect { analytics.alias :previous_id => 'foo', user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.alias :previous_id => 'foo', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do @@ -57,6 +63,8 @@ class Analytics it 'errors without user_id or anonymous_id' do expect { analytics.group :group_id => 'foo' }.to raise_error(ArgumentError) + expect { analytics.group :group_id => 'foo', user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.group :group_id => 'foo', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do @@ -70,6 +78,8 @@ class Analytics describe '#page' do it 'errors without user_id or anonymous_id' do expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError) + expect { analytics.page :name => 'foo', user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.page :name => 'foo', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do @@ -83,6 +93,8 @@ class Analytics describe '#screen' do it 'errors without user_id or anonymous_id' do expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError) + expect { analytics.screen :name => 'foo', user_id: '1234' }.to_not raise_error(ArgumentError) + expect { analytics.screen :name => 'foo', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end it 'does not error with the required options' do From 5da40adbbc7c8eb9a07bea3b88c93dee8cb5b7bc Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 19 Apr 2019 12:58:32 +0530 Subject: [PATCH 232/322] Add failing test for optional :name in #page --- spec/segment/analytics/client_spec.rb | 10 ++++++---- spec/spec_helper.rb | 4 +--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 178eb13..d6d8225 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -224,10 +224,12 @@ class Analytics expect { client.page Queued::PAGE }.to_not raise_error end - it 'does not error with the required options as strings' do - expect do - client.page Utils.stringify_keys(Queued::PAGE) - end.to_not raise_error + it 'accepts name' do + client.page :name => 'foo', :user_id => 1234 + + message = queue.pop + expect(message[:userId]).to eq(1234) + expect(message[:name]).to eq('foo') end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8bc41b0..847acea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -39,9 +39,7 @@ class Analytics GROUP = {} - PAGE = { - :name => 'home' - } + PAGE = {} SCREEN = { :name => 'main' From b59720a377296db719e9f1d4ddf7557f9a23531c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 19 Apr 2019 13:01:30 +0530 Subject: [PATCH 233/322] Make name non-mandatory in #page This was a bug, introduced in #188 --- lib/segment/analytics/field_parser.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 4f142f7..378c730 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -89,10 +89,9 @@ def parse_for_group(fields) def parse_for_page(fields) common = parse_common_fields(fields) - name = fields[:name] + name = fields[:name] || '' properties = fields[:properties] || {} - check_presence!(name, 'name') check_is_hash!(properties, 'properties') isoify_dates! properties From 343e3d186804735500269b094a880bce481f0059 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 9 May 2019 18:53:03 +0530 Subject: [PATCH 234/322] Add new release instructions This ensures that pre-releases and their promoted versions contain the exact same code. --- RELEASING.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 2a60d9c..a13353d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,9 +1,24 @@ -Releasing -========= +Pre-Releases +============ 1. Verify everything works with `make check build`. - 2. Bump version in [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). - 3. Update [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). - 4. Commit and tag `git commit -am "Release {version}" && git tag -a {version} -m "Version {version}"`. - 5. Upload to Github with `git push -u origin master && git push --tags`. -The tagged commit will be pushed to RubyGems via Travis. + 2. Bump version in + [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). + This version string should not include a `.pre` suffix, as the same commit will + be re-tagged when the pre-release is promoted. + 3. Update + [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). + 4. Commit and tag the pre-release. `git commit -am "Release {version.pre}" && + git tag -a {version.pre} -m "Version {version.pre}"`. + 5. Upload to Github with `git push -u origin master && git push --tags`. The + tagged commit will be pushed to RubyGems via Travis. + +Promoting Pre-releases +====================== + +- Find the tag for the pre-release you want to promote. `git tag --list + '*.pre'` +- Re-tag this commit without the `.pre` prefix. `git tag -a -m "Version + {version}" {version} {pre_version}` +- Upload to Github with `git push --tags`. The tagged commit will be pushed to + RubyGems via Travis. From 95ba917df0500b509084e45d76e81ae90c646f49 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 9 May 2019 18:58:03 +0530 Subject: [PATCH 235/322] Bump version and update history for 2.2.7.pre --- History.md | 6 ++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c776a65..34be271 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +2.2.7.pre / 2019-05-09 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/188): Allow `anonymous_id` + in `#alias` and `#group`. + 2.2.6 / 2018-06-11 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 70aa0a5..9a4addf 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.6' + VERSION = '2.2.7' end end From 3381f7f49cd1f71bd7bf09099f625a093360bddc Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 24 May 2019 19:55:52 +0530 Subject: [PATCH 236/322] Update history to reflect 2.2.7 release Due to a bug in the release script, we accidentally released 2.2.7 directly instead of going through a pre-release. --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 34be271..7f400e7 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -2.2.7.pre / 2019-05-09 +2.2.7 / 2019-05-09 ================== * [Fix](https://github.com/segmentio/analytics-ruby/pull/188): Allow `anonymous_id` From 0e62d149e9e02855613666b82992365339eec581 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 27 May 2019 21:17:15 +0530 Subject: [PATCH 237/322] Upgrade tester version --- .buildscript/e2e.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildscript/e2e.sh b/.buildscript/e2e.sh index ed25afe..adc5c66 100755 --- a/.buildscript/e2e.sh +++ b/.buildscript/e2e.sh @@ -6,7 +6,7 @@ if [ "$RUN_E2E_TESTS" != "true" ]; then echo "Skipping end to end tests." else echo "Running end to end tests..." - wget https://github.com/segmentio/library-e2e-tester/releases/download/0.2.1/tester_linux_amd64 -O tester + wget https://github.com/segmentio/library-e2e-tester/releases/download/0.4.0/tester_linux_amd64 -O tester chmod +x tester ./tester -path='./bin/analytics' echo "End to end tests completed!" From 8b4dca9b81fd9366fdd3119d370d1fc03c42f2d3 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 27 May 2019 21:28:32 +0530 Subject: [PATCH 238/322] Support --integrations in CLI for e2e tests --- bin/analytics | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bin/analytics b/bin/analytics index e9e107e..1ba79db 100755 --- a/bin/analytics +++ b/bin/analytics @@ -29,6 +29,7 @@ command :send do |c| c.option '--userId=', String, 'the user id to send the event as' c.option '--anonymousId=', String, 'the anonymous user id to send the event as' c.option '--context=', 'additional context for the event (JSON-encoded)' + c.option '--integrations=', 'additional integrations for the event (JSON-encoded)' c.option '--event=', String, 'the event name to send with the event' c.option '--properties=', 'the event properties to send (JSON-encoded)' @@ -52,7 +53,8 @@ command :send do |c| event: options.event, anonymous_id: options.anonymousId, properties: json_hash(options.properties), - context: json_hash(options.context) + context: json_hash(options.context), + integrations: json_hash(options.integrations) }) when "page" Analytics.page({ @@ -60,22 +62,25 @@ command :send do |c| anonymous_id: options.anonymousId, name: options.name, properties: json_hash(options.properties), - context: json_hash(options.context) + context: json_hash(options.context), + integrations: json_hash(options.integrations) }) when "screen" Analytics.screen({ user_id: options.userId, anonymous_id: options.anonymousId, - name: option.name, - traits: json_hash(options.traits), - properties: json_hash(option.properties) + name: options.name, + properties: json_hash(options.properties), + context: json_hash(options.context), + integrations: json_hash(options.integrations) }) when "identify" Analytics.identify({ user_id: options.userId, anonymous_id: options.anonymousId, traits: json_hash(options.traits), - context: json_hash(options.context) + context: json_hash(options.context), + integrations: json_hash(options.integrations) }) when "group" Analytics.group({ @@ -83,7 +88,8 @@ command :send do |c| anonymous_id: options.anonymousId, group_id: options.groupId, traits: json_hash(options.traits), - context: json_hash(options.context) + context: json_hash(options.context), + integrations: json_hash(options.integrations) }) else raise "Invalid Message Type #{options.type}" From c13ff43a3ef11895de728faf492cb817d808e2b4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 27 May 2019 21:38:51 +0530 Subject: [PATCH 239/322] Support 'alias' calls in e2e tests --- bin/analytics | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bin/analytics b/bin/analytics index 1ba79db..86f3511 100755 --- a/bin/analytics +++ b/bin/analytics @@ -39,6 +39,7 @@ command :send do |c| c.option '--traits=', 'the identify/group traits to send (JSON-encoded)' c.option '--groupId=', String, 'the group id' + c.option '--previousId=', String, 'the previous id' c.action do |args, options| Analytics = Segment::Analytics.new({ @@ -91,6 +92,14 @@ command :send do |c| context: json_hash(options.context), integrations: json_hash(options.integrations) }) + when "alias" + Analytics.alias({ + previous_id: options.previousId, + user_id: options.userId, + anonymous_id: options.anonymousId, + context: json_hash(options.context), + integrations: json_hash(options.integrations) + }) else raise "Invalid Message Type #{options.type}" end From bc71efc5e8663d9620415eabe89551213de20c1c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 27 May 2019 22:04:28 +0530 Subject: [PATCH 240/322] Don't send fields if they're nil --- lib/segment/analytics/field_parser.rb | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 378c730..7918710 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -120,12 +120,15 @@ def parse_for_screen(fields) isoify_dates! properties - common.merge({ + parsed = common.merge({ :type => 'screen', :name => name, - :properties => properties, - :category => category + :properties => properties }) + + parsed[:category] = category if category + + parsed end private @@ -140,15 +143,20 @@ def parse_common_fields(fields) add_context! context - { - :anonymousId => fields[:anonymous_id], + parsed = { :context => context, - :integrations => fields[:integrations], :messageId => message_id, - :timestamp => datetime_in_iso8601(timestamp), - :userId => fields[:user_id], - :options => fields[:options] # Not in spec, retained for backward compatibility + :timestamp => datetime_in_iso8601(timestamp) } + + parsed[:userId] = fields[:user_id] if fields[:user_id] + parsed[:anonymousId] = fields[:anonymous_id] if fields[:anonymous_id] + parsed[:integrations] = fields[:integrations] if fields[:integrations] + + # Not in spec, retained for backward compatibility + parsed[:options] = fields[:options] if fields[:options] + + parsed end def check_user_id!(fields) From 39aad129f292525bd63ea48f2b1a8713c45c76a1 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 27 May 2019 22:29:53 +0530 Subject: [PATCH 241/322] Use wget's quiet mode when downloading tester --- .buildscript/e2e.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildscript/e2e.sh b/.buildscript/e2e.sh index adc5c66..a634eda 100755 --- a/.buildscript/e2e.sh +++ b/.buildscript/e2e.sh @@ -6,7 +6,7 @@ if [ "$RUN_E2E_TESTS" != "true" ]; then echo "Skipping end to end tests." else echo "Running end to end tests..." - wget https://github.com/segmentio/library-e2e-tester/releases/download/0.4.0/tester_linux_amd64 -O tester + wget -q https://github.com/segmentio/library-e2e-tester/releases/download/0.4.0/tester_linux_amd64 -O tester chmod +x tester ./tester -path='./bin/analytics' echo "End to end tests completed!" From 540af902db80d94742267608481f44b70b096476 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 5 Jun 2019 17:21:11 +0530 Subject: [PATCH 242/322] Fix steps in RELEASING.md for pre-releases --- RELEASING.md | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index a13353d..b6041eb 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,24 +1,28 @@ -Pre-Releases +We automatically push tags to Rubygems via CI. + +Pre-releases ============ - 1. Verify everything works with `make check build`. - 2. Bump version in - [`version.rb`](https://github.com/segmentio/analytics-ruby/blob/master/lib/segment/analytics/version.rb). - This version string should not include a `.pre` suffix, as the same commit will - be re-tagged when the pre-release is promoted. - 3. Update - [`History.md`](https://github.com/segmentio/analytics-ruby/blob/master/History.md). - 4. Commit and tag the pre-release. `git commit -am "Release {version.pre}" && - git tag -a {version.pre} -m "Version {version.pre}"`. - 5. Upload to Github with `git push -u origin master && git push --tags`. The - tagged commit will be pushed to RubyGems via Travis. +- Make sure you're on the latest `master` +- Bump the version in [`version.rb`](lib/segment/analytics/version.rb) +- Update [`History.md`](History.md) +- Commit these changes. `git commit -am "Release x.y.z.pre"` +- Tag the pre-release. `git tag -a -m "Version x.y.z.pre" x.y.z.pre` +- `git push -u origin master && git push --tags`. The tagged commit will be + pushed to RubyGems via Travis + -Promoting Pre-releases +Promoting pre-releases ====================== -- Find the tag for the pre-release you want to promote. `git tag --list - '*.pre'` -- Re-tag this commit without the `.pre` prefix. `git tag -a -m "Version - {version}" {version} {pre_version}` -- Upload to Github with `git push --tags`. The tagged commit will be pushed to - RubyGems via Travis. +- Find the tag for the pre-release you want to promote. Use `git tag --list + '*.pre'` to list all pre-release tags +- Checkout that tag. `git checkout tags/x.y.z.pre` +- Update the version in [`version.rb`](lib/segment/analytics/version.rb) to not + include the `.pre` suffix +- Commit these changes. `git commit -am "Promote x.y.z.pre"` +- Tag the release. `git tag -a -m "Version x.y.z" x.y.z` +- `git push -u origin master && git push --tags`. The tagged commit will be + pushed to RubyGems via Travis +- On `master`, add an entry to [`History.md`](History.md) under `x.y.z` that + says 'Promoted pre-release to stable' From 92a3594edc39ad93ebcda3841dc67389e66b6913 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 18 Jul 2019 11:15:15 +0530 Subject: [PATCH 243/322] Remove 1.9.3 from test matrix Travis doesn't support 1.9.3 anymore, as it looks for RVM binaries and they aren't available for 14.04. Ref: https://rvm.io/binaries/ubuntu/16.04/x86_64/ --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5cc960c..6750bc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: ruby rvm: - jruby-19mode - - 1.9.3 - 2.0.0 - 2.1.10 - 2.2.9 From 4a135fbddc42c4dd2c37be57758eb18b4fd5c7f4 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 29 Jul 2019 21:31:37 +0530 Subject: [PATCH 244/322] Add coverage/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 83f5868..ab09e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.gem Gemfile.lock .ruby-version +coverage/ From 81b825e4d751f1fff37e0df83229aa92713cb18f Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 29 Jul 2019 22:08:42 +0530 Subject: [PATCH 245/322] Handle serialization errors, isolate bad events This commit adds error handling for JSON generation, and ensures that one bad event doesn't affect other good events in a batch. Fixes https://github.com/segmentio/analytics-ruby/issues/180 --- lib/segment/analytics/message_batch.rb | 9 ++++++++- lib/segment/analytics/worker.rb | 11 +++++++++-- spec/segment/analytics/worker_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 2e9385d..1522463 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -5,6 +5,8 @@ module Segment class Analytics # A batch of `Message`s to be sent to the API class MessageBatch + class JSONGenerationError < StandardError; end + extend Forwardable include Segment::Analytics::Logging include Segment::Analytics::Defaults::MessageBatch @@ -16,8 +18,13 @@ def initialize(max_message_count) end def <<(message) - message_json_size = message.to_json.bytesize + begin + message_json = message.to_json + rescue StandardError => e + raise JSONGenerationError, "Serialization error: #{e}" + end + message_json_size = message_json.bytesize if message_too_big?(message_json_size) logger.error('a message exceeded the maximum allowed size') else diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 2f86dd4..ced52e9 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -38,11 +38,10 @@ def run return if @queue.empty? @lock.synchronize do - @batch << @queue.pop until @batch.full? || @queue.empty? + consume_message_from_queue! until @batch.full? || @queue.empty? end res = Request.new.post @write_key, @batch - @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } @@ -54,6 +53,14 @@ def run def is_requesting? @lock.synchronize { !@batch.empty? } end + + private + + def consume_message_from_queue! + @batch << @queue.pop + rescue MessageBatch::JSONGenerationError => e + @on_error.call(-1, e.to_s) + end end end end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index e66db64..eb5de20 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -83,6 +83,29 @@ class Analytics expect(queue).to be_empty end + + it 'calls on_error for bad json' do + bad_obj = Object.new + def bad_obj.to_json(*_args) + raise "can't serialize to json" + end + + on_error = proc {} + expect(on_error).to receive(:call).once.with(-1, /serialize to json/) + + good_message = Requested::TRACK + bad_message = Requested::TRACK.merge({ 'bad_obj' => bad_obj }) + + queue = Queue.new + queue << good_message + queue << bad_message + + worker = described_class.new(queue, + 'testsecret', + :on_error => on_error) + worker.run + expect(queue).to be_empty + end end describe '#is_requesting?' do From 5da703e7d64afe4943d49098368b37ebee09cbc0 Mon Sep 17 00:00:00 2001 From: Kyle VanderBeek Date: Sat, 7 Sep 2019 13:27:50 -0700 Subject: [PATCH 246/322] Explicitly initialize @worker_thread Without this, some modern rubies will emit this warning: /usr/local/bundle/gems/analytics-ruby-2.2.7/lib/segment/analytics/client.rb:31: warning: instance variable @worker_thread not initialized --- lib/segment/analytics/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index e83c8d8..61ee386 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -25,6 +25,7 @@ def initialize(opts = {}) @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE @worker_mutex = Mutex.new @worker = Worker.new(@queue, @write_key, opts) + @worker_thread = nil check_write_key! From e84b6fc72a0a72d39f328878464a98c82c4b2a22 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 6 Oct 2019 02:34:42 +0530 Subject: [PATCH 247/322] Set minimum ruby version to 2.0 + minor code/docs cleanup in areas that were 1.9-specific --- .rubocop.yml | 6 +----- .travis.yml | 1 - analytics-ruby.gemspec | 4 +--- spec/segment/analytics/client_spec.rb | 8 ++++---- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index d9751fd..a043e4f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ inherit_from: .rubocop_todo.yml AllCops: - # Rubocop doesn't support 1.9, so we'll use the minimum available + # Rubocop doesn't support 2.0, so we'll use the minimum available TargetRubyVersion: 2.1 Layout/IndentHash: @@ -84,7 +84,3 @@ Style/ParallelAssignment: Style/PreferredHashMethods: EnforcedStyle: verbose - -# Ruby 1.9 doesn't support percent-styled symbol arrays -Style/SymbolArray: - EnforcedStyle: brackets diff --git a/.travis.yml b/.travis.yml index 6750bc0..cadd54c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: ruby rvm: - - jruby-19mode - 2.0.0 - 2.1.10 - 2.2.9 diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index ba0c3fe..60d9155 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -13,9 +13,7 @@ Gem::Specification.new do |spec| spec.email = 'friends@segment.io' spec.homepage = 'https://github.com/segmentio/analytics-ruby' spec.license = 'MIT' - - # Ruby 1.8 requires json - spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9" + spec.required_ruby_version = '>= 2.0' # Used in the executable testing script spec.add_development_dependency 'commander', '~> 4.4' diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index d6d8225..e4ffd60 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -281,7 +281,7 @@ class Analytics let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => 'coco barked', :name => 'coco' } } it 'does not convert ids given as fixnums to strings' do - [:track, :screen, :page, :identify].each do |s| + %i[track screen page identify].each do |s| client.send(s, data) message = queue.pop(true) @@ -293,7 +293,7 @@ class Analytics it 'returns false if queue is full' do client.instance_variable_set(:@max_queue_size, 1) - [:track, :screen, :page, :group, :identify, :alias].each do |s| + %i[track screen page group identify alias].each do |s| expect(client.send(s, data)).to eq(true) expect(client.send(s, data)).to eq(false) # Queue is full queue.pop(true) @@ -301,7 +301,7 @@ class Analytics end it 'converts message id to string' do - [:track, :screen, :page, :group, :identify, :alias].each do |s| + %i[track screen page group identify alias].each do |s| client.send(s, data) message = queue.pop(true) @@ -330,7 +330,7 @@ class Analytics end it 'sends integrations' do - [:track, :screen, :page, :group, :identify, :alias].each do |s| + %i[track screen page group identify alias].each do |s| client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => 'coco barked', :name => 'coco' message = queue.pop(true) expect(message[:integrations][:All]).to eq(true) From d3e831ec31a4744b373a861bf7e72d6a472ce499 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 6 Oct 2019 02:56:50 +0530 Subject: [PATCH 248/322] Refresh .rubocop_todo.yml --- .rubocop_todo.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 17dd033..11a76cc 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,39 +1,34 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-11-23 15:34:00 +0530 using RuboCop version 0.51.0. +# on 2019-10-06 02:55:44 +0530 using RuboCop version 0.51.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -Lint/RescueException: - Exclude: - - 'lib/segment/analytics/request.rb' - -# Offense count: 9 +# Offense count: 4 Metrics/AbcSize: - Max: 32 + Max: 24 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 231 + Max: 114 # Offense count: 1 Metrics/CyclomaticComplexity: Max: 8 -# Offense count: 36 +# Offense count: 8 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: - Max: 223 + Max: 147 -# Offense count: 9 +# Offense count: 10 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 29 + Max: 16 # Offense count: 1 Metrics/PerceivedComplexity: From 7a2439cedb1d7632561327f314bb9e146632ca53 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 6 Oct 2019 03:30:34 +0530 Subject: [PATCH 249/322] Avoid network calls in tests These tests were sending HTTP requests to Segment. Added mocks/stubs at the right places to prevent this from happening. --- spec/segment/analytics/client_spec.rb | 7 ++++++- spec/segment/analytics/worker_spec.rb | 22 +++++++++++++++++++--- spec/spec_helper.rb | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index d6d8225..496545b 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -250,7 +250,12 @@ class Analytics end describe '#flush' do - let(:client_with_worker) { Client.new(:write_key => WRITE_KEY) } + let(:client_with_worker) { + Client.new(:write_key => WRITE_KEY).tap { |client| + queue = client.instance_variable_get(:@queue) + client.instance_variable_set(:@worker, DummyWorker.new(queue)) + } + } it 'waits for the queue to finish on a flush' do client_with_worker.identify Queued::IDENTIFY diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index eb5de20..ca6dcd0 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -3,6 +3,10 @@ module Segment class Analytics describe Worker do + before do + Segment::Analytics::Request.stub = true + end + describe '#init' do it 'accepts string keys' do queue = Queue.new @@ -23,9 +27,12 @@ class Analytics Segment::Analytics::Defaults::Request::BACKOFF = 30.0 end - it 'does not error if the endpoint is unreachable' do + it 'does not error if the request fails' do expect do - Net::HTTP.any_instance.stub(:post).and_raise(Exception) + Segment::Analytics::Request + .any_instance + .stub(:post) + .and_return(Segment::Analytics::Response.new(-1, 'Unknown error')) queue = Queue.new queue << {} @@ -34,7 +41,7 @@ class Analytics expect(queue).to be_empty - Net::HTTP.any_instance.unstub(:post) + Segment::Analytics::Request.any_instance.unstub(:post) end.to_not raise_error end @@ -117,6 +124,13 @@ def bad_obj.to_json(*_args) end it 'returns true if there is a current batch' do + Segment::Analytics::Request + .any_instance + .stub(:post) { + sleep(0.2) + Segment::Analytics::Response.new(200, 'Success') + } + queue = Queue.new queue << Requested::TRACK worker = Segment::Analytics::Worker.new(queue, 'testsecret') @@ -126,6 +140,8 @@ def bad_obj.to_json(*_args) worker_thread.join expect(worker.is_requesting?).to eq(false) + + Segment::Analytics::Request.any_instance.unstub(:post) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 847acea..a0f9247 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -88,6 +88,21 @@ def run end end +# A worker that consumes all jobs +class DummyWorker + def initialize(queue) + @queue = queue + end + + def run + @queue.pop until @queue.empty? + end + + def is_requesting? + false + end +end + # A backoff policy that returns a fixed list of values class FakeBackoffPolicy def initialize(interval_values) From 6bba4b756229b01bfa6b6cac4b9283f874bf2822 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 26 Nov 2019 18:51:54 +0400 Subject: [PATCH 250/322] Fix stubbed request message --- lib/segment/analytics/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/request.rb index 5fcb1c2..344ab75 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/request.rb @@ -113,7 +113,7 @@ def send_request(write_key, batch) if self.class.stub logger.debug "stubbed request to #{@path}: " \ - "write key = #{write_key}, batch = JSON.generate(#{batch})" + "write key = #{write_key}, batch = #{JSON.generate(batch)}" [200, '{}'] else From 25edc53447f6bf404251318e999d8cb9ce60789e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 29 Nov 2019 23:02:46 +0530 Subject: [PATCH 251/322] Release 2.2.8.pre --- History.md | 8 ++++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 7f400e7..103a005 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,11 @@ +2.2.8.pre / 2019-11-29 +================== + + * [Fix](https://github.com/segmentio/analytics-ruby/pull/212): Fix log message + for stubbed requests + * [Deprecate](https://github.com/segmentio/analytics-ruby/pull/209): Deprecate + Ruby <2.0 support + 2.2.7 / 2019-05-09 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 9a4addf..5f3772e 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.7' + VERSION = '2.2.8.pre' end end From 6205ce3678123717be68e55b140ddcd113de0f89 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Fri, 6 Dec 2019 12:28:43 +0530 Subject: [PATCH 252/322] Re-use TCP connections across API calls - Rename Analytics::Request -> Analytics::Transport - Rename Transport#post -> Transport#send - Add Tranport#shutdown to close persistent connections - Re-use Transport object in Analytics::Worker --- lib/segment/analytics.rb | 4 +-- .../analytics/{request.rb => transport.rb} | 16 ++++++---- lib/segment/analytics/worker.rb | 7 +++-- .../{request_spec.rb => transport_spec.rb} | 29 ++++++++++--------- spec/segment/analytics/worker_spec.rb | 20 ++++++------- 5 files changed, 42 insertions(+), 34 deletions(-) rename lib/segment/analytics/{request.rb => transport.rb} (91%) rename spec/segment/analytics/{request_spec.rb => transport_spec.rb} (90%) diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index d6f1df8..1e47c62 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -4,7 +4,7 @@ require 'segment/analytics/field_parser' require 'segment/analytics/client' require 'segment/analytics/worker' -require 'segment/analytics/request' +require 'segment/analytics/transport' require 'segment/analytics/response' require 'segment/analytics/logging' @@ -18,7 +18,7 @@ class Analytics # @option options [Boolean] :stub (false) If true, requests don't hit the # server and are stubbed to be successful. def initialize(options = {}) - Request.stub = options[:stub] if options.has_key?(:stub) + Transport.stub = options[:stub] if options.has_key?(:stub) @client = Segment::Analytics::Client.new options end diff --git a/lib/segment/analytics/request.rb b/lib/segment/analytics/transport.rb similarity index 91% rename from lib/segment/analytics/request.rb rename to lib/segment/analytics/transport.rb index 344ab75..59697be 100644 --- a/lib/segment/analytics/request.rb +++ b/lib/segment/analytics/transport.rb @@ -9,13 +9,11 @@ module Segment class Analytics - class Request + class Transport include Segment::Analytics::Defaults::Request include Segment::Analytics::Utils include Segment::Analytics::Logging - # public: Creates a new request object to send analytics batch - # def initialize(options = {}) options[:host] ||= HOST options[:port] ||= PORT @@ -34,10 +32,10 @@ def initialize(options = {}) @http = http end - # public: Posts the write key and batch of messages to the API. + # Sends a batch of messages to the API # - # returns - Response of the status and error if it exists - def post(write_key, batch) + # @return [Response] API response + def send(write_key, batch) logger.debug("Sending request for #{batch.length} items") last_response, exception = retry_with_backoff(@retries) do @@ -59,6 +57,11 @@ def post(write_key, batch) end end + # Closes a persistent connection if it exists + def shutdown + @http.finish if @http.started? + end + private def should_retry_request?(status_code, body) @@ -117,6 +120,7 @@ def send_request(write_key, batch) [200, '{}'] else + @http.start unless @http.started? # Maintain a persistent connection response = @http.request(request, payload) [response.code.to_i, response.body] end diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index ced52e9..a313eed 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,6 +1,6 @@ require 'segment/analytics/defaults' require 'segment/analytics/message_batch' -require 'segment/analytics/request' +require 'segment/analytics/transport' require 'segment/analytics/utils' module Segment @@ -29,6 +29,7 @@ def initialize(queue, write_key, options = {}) batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE @batch = MessageBatch.new(batch_size) @lock = Mutex.new + @transport = Transport.new end # public: Continuously runs the loop to check for new events @@ -41,11 +42,13 @@ def run consume_message_from_queue! until @batch.full? || @queue.empty? end - res = Request.new.post @write_key, @batch + res = @transport.send @write_key, @batch @on_error.call(res.status, res.error) unless res.status == 200 @lock.synchronize { @batch.clear } end + ensure + @transport.shutdown end # public: Check whether we have outstanding requests. diff --git a/spec/segment/analytics/request_spec.rb b/spec/segment/analytics/transport_spec.rb similarity index 90% rename from spec/segment/analytics/request_spec.rb rename to spec/segment/analytics/transport_spec.rb index b4d0f63..56b551f 100644 --- a/spec/segment/analytics/request_spec.rb +++ b/spec/segment/analytics/transport_spec.rb @@ -2,7 +2,7 @@ module Segment class Analytics - describe Request do + describe Transport do before do # Try and keep debug statements out of tests allow(subject.logger).to receive(:error) @@ -98,7 +98,7 @@ class Analytics end end - describe '#post' do + describe '#send' do let(:response) { Net::HTTPResponse.new(http_version, status_code, response_body) } @@ -110,6 +110,7 @@ class Analytics before do http = subject.instance_variable_get(:@http) + allow(http).to receive(:start) allow(http).to receive(:request) { response } allow(response).to receive(:body) { response_body } end @@ -125,14 +126,14 @@ class Analytics path, default_headers ).and_call_original - subject.post(write_key, batch) + subject.send(write_key, batch) end it 'adds basic auth to the Net::HTTP::Post' do expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth) .with(write_key, nil) - subject.post(write_key, batch) + subject.send(write_key, batch) end context 'with a stub' do @@ -141,16 +142,16 @@ class Analytics end it 'returns a 200 response' do - expect(subject.post(write_key, batch).status).to eq(200) + expect(subject.send(write_key, batch).status).to eq(200) end it 'has a nil error' do - expect(subject.post(write_key, batch).error).to be_nil + expect(subject.send(write_key, batch).error).to be_nil end it 'logs a debug statement' do expect(subject.logger).to receive(:debug).with(/stubbed request to/) - subject.post(write_key, batch) + subject.send(write_key, batch) end end @@ -171,7 +172,7 @@ class Analytics .exactly(retries - 1).times .with(1) .and_return(nil) - subject.post(write_key, batch) + subject.send(write_key, batch) end end @@ -186,18 +187,18 @@ class Analytics expect(subject) .to receive(:sleep) .never - subject.post(write_key, batch) + subject.send(write_key, batch) end end context 'request is successful' do let(:status_code) { 201 } it 'returns a response code' do - expect(subject.post(write_key, batch).status).to eq(status_code) + expect(subject.send(write_key, batch).status).to eq(status_code) end it 'returns a nil error' do - expect(subject.post(write_key, batch).error).to be_nil + expect(subject.send(write_key, batch).error).to be_nil end end @@ -206,7 +207,7 @@ class Analytics let(:response_body) { { error: error }.to_json } it 'returns the parsed error' do - expect(subject.post(write_key, batch).error).to eq(error) + expect(subject.send(write_key, batch).error).to eq(error) end end @@ -227,11 +228,11 @@ class Analytics subject { described_class.new(retries: 0) } it 'returns a -1 for status' do - expect(subject.post(write_key, batch).status).to eq(-1) + expect(subject.send(write_key, batch).status).to eq(-1) end it 'has a connection error' do - error = subject.post(write_key, batch).error + error = subject.send(write_key, batch).error expect(error).to match(/Malformed JSON/) end diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index ca6dcd0..0ca4d49 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -4,7 +4,7 @@ module Segment class Analytics describe Worker do before do - Segment::Analytics::Request.stub = true + Segment::Analytics::Transport.stub = true end describe '#init' do @@ -29,9 +29,9 @@ class Analytics it 'does not error if the request fails' do expect do - Segment::Analytics::Request + Segment::Analytics::Transport .any_instance - .stub(:post) + .stub(:send) .and_return(Segment::Analytics::Response.new(-1, 'Unknown error')) queue = Queue.new @@ -41,14 +41,14 @@ class Analytics expect(queue).to be_empty - Segment::Analytics::Request.any_instance.unstub(:post) + Segment::Analytics::Transport.any_instance.unstub(:send) end.to_not raise_error end it 'executes the error handler if the request is invalid' do - Segment::Analytics::Request + Segment::Analytics::Transport .any_instance - .stub(:post) + .stub(:send) .and_return(Segment::Analytics::Response.new(400, 'Some error')) status = error = nil @@ -67,7 +67,7 @@ class Analytics sleep 0.1 # First give thread time to spin-up. sleep 0.01 while worker.is_requesting? - Segment::Analytics::Request.any_instance.unstub(:post) + Segment::Analytics::Transport.any_instance.unstub(:send) expect(queue).to be_empty expect(status).to eq(400) @@ -124,9 +124,9 @@ def bad_obj.to_json(*_args) end it 'returns true if there is a current batch' do - Segment::Analytics::Request + Segment::Analytics::Transport .any_instance - .stub(:post) { + .stub(:send) { sleep(0.2) Segment::Analytics::Response.new(200, 'Success') } @@ -141,7 +141,7 @@ def bad_obj.to_json(*_args) worker_thread.join expect(worker.is_requesting?).to eq(false) - Segment::Analytics::Request.any_instance.unstub(:post) + Segment::Analytics::Transport.any_instance.unstub(:send) end end end From b48dfa9ae3124a372beb99e1135e40cbade6a18c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Mon, 10 Feb 2020 16:50:21 +0530 Subject: [PATCH 253/322] Update History.md to reflect 2.2.8 release --- History.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/History.md b/History.md index 103a005..f703a72 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.2.8 / 2020-02-10 +================== + + * Promoted pre-release version to stable. + 2.2.8.pre / 2019-11-29 ================== From bde3d65d5483afc7add58bcbecd90615702b4635 Mon Sep 17 00:00:00 2001 From: Kyle VanderBeek Date: Wed, 19 Feb 2020 17:07:38 -0800 Subject: [PATCH 254/322] BitDeli is gone, remove badge from README. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 2c44245..b0b551f 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,3 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/segmentio/analytics-ruby/trend.png)](https://bitdeli.com/free "Bitdeli Badge") - From 0fcb338938e59c7cbe9581ef3f81642182a550cb Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 24 Mar 2021 16:53:02 -0500 Subject: [PATCH 255/322] Changed the fractional digits to display 6 places instead of the original 3 --- lib/segment/analytics/utils.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 60bfa52..745a5e4 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -64,12 +64,8 @@ def datetime_in_iso8601(datetime) end end - def time_in_iso8601(time, fraction_digits = 3) - fraction = if fraction_digits > 0 - ('.%06i' % time.usec)[0, fraction_digits + 1] - end - - "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}" + def time_in_iso8601(time) + "#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}" end def date_in_iso8601(date) From 7aa3f1df29f26be1cc1d9f8d0ef36143cbbdae4e Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Fri, 26 Mar 2021 11:16:31 -0500 Subject: [PATCH 256/322] Issue #226 Update supported Ruby Versions --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cadd54c..d015dd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: ruby rvm: - - 2.0.0 - - 2.1.10 - - 2.2.9 - - 2.3.6 - 2.4.3 - - 2.5.0 # Performs deploys. Change condition below when changing this. + - 2.5.0 + - 2.6.0 + - 2.7.0 # Performs deploys. Change condition below when changing this. script: - make check @@ -17,6 +15,6 @@ deploy: provider: rubygems on: tags: true - condition: "$TRAVIS_RUBY_VERSION == 2.5.0" + condition: "$TRAVIS_RUBY_VERSION == 2.7.0" api_key: secure: Ceq6J4aBpsoqRfSiC7z+/J4moOXNjcPMFb2Bfm5qE51cIZzeyuOIOc6zhrad9tUgoX6uTRRxLxkybyu4wNYSluMA3IXW20CJyXZeJEHIaTYIDTWFAIYyerBJyMujJycSo7XueWb0faKBENrBQKx1K1tS0EiXpA2rMhdA6RM3DOY= From 6c998346ca4d0f8f050674444e62474ba64dfe75 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Fri, 26 Mar 2021 11:24:15 -0500 Subject: [PATCH 257/322] Update version file --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 5f3772e..767c2cd 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.8.pre' + VERSION = '2.2.8' end end From 91799d8a28b31ced1d25cd2a8953156d35499dc3 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 24 Mar 2021 16:53:02 -0500 Subject: [PATCH 258/322] Changed the fractional digits to display 6 places instead of the original 3 --- lib/segment/analytics/utils.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 60bfa52..745a5e4 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -64,12 +64,8 @@ def datetime_in_iso8601(datetime) end end - def time_in_iso8601(time, fraction_digits = 3) - fraction = if fraction_digits > 0 - ('.%06i' % time.usec)[0, fraction_digits + 1] - end - - "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}" + def time_in_iso8601(time) + "#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}" end def date_in_iso8601(date) From a8558ea73792d52d01f55d34f8e6fa6cf688a5f2 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Fri, 26 Mar 2021 12:07:35 -0500 Subject: [PATCH 259/322] Minor version bump with history --- History.md | 6 ++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index f703a72..3d888f2 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +2.3.0 / 2020-03-26 +================== + + * [Improvement](https://github.com/segmentio/analytics-ruby/pull/225): Update timestamp for sub-millisecond reporting + * Update supported Ruby versions (2.4, 2.5, 2.6, 2.7), remove unsupported Ruby versions (2.0, 2.1, 2.2, 2.3) + 2.2.8 / 2020-02-10 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 767c2cd..62d2fa0 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.2.8' + VERSION = '2.3.0' end end From 7305134b9231099e152bdc0c7de4c63024113ea7 Mon Sep 17 00:00:00 2001 From: Pooya Jaferian Date: Fri, 26 Mar 2021 13:36:41 -0700 Subject: [PATCH 260/322] release 2.3.0 From 46df274056adf9f226412ea5dabf58959a8c7959 Mon Sep 17 00:00:00 2001 From: Ryan Jackson Date: Fri, 19 Feb 2021 13:40:59 +0100 Subject: [PATCH 261/322] Add test option and queue for easier testing --- README.md | 73 +++++++- lib/segment/analytics.rb | 1 + lib/segment/analytics/client.rb | 11 ++ lib/segment/analytics/test_queue.rb | 56 ++++++ spec/segment/analytics/test_queue_spec.rb | 213 ++++++++++++++++++++++ spec/segment/analytics_spec.rb | 27 +++ 6 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 lib/segment/analytics/test_queue.rb create mode 100644 spec/segment/analytics/test_queue_spec.rb diff --git a/README.md b/README.md index b0b551f..f3b3d5a 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,78 @@ Documentation is available at [segment.com/docs/sources/server/ruby](https://seg ## Testing -You can use the `stub` option to Segment::Analytics.new to cause all requests to be stubbed, making it easier to test with this library. +You can use the `stub: true` option to Segment::Analytics.new to cause all requests to be stubbed, making it easier to test with this library. + +### Test Queue + +You can use the `test: true` option to Segment::Analytics.new to cause all requests to be saved to a test queue until manually reset. All events will process as specified by the configuration, and they will also be stored in a separate queue for inspection during testing. + +A test queue can be used as follows: + +```ruby +client = Segment::Analytics.new(test: true) + +client.test_queue # => # + +client.track(user_id: "foo", event: "bar") + +client.test_queue.all +# [ +# { +# :context => { +# :library => { +# :name => "analytics-ruby", +# :version => "2.2.8.pre" +# } +# }, +# :messageId => "e9754cc0-1c5e-47e4-832a-203589d279e4", +# :timestamp => "2021-02-19T13:32:39.547+01:00", +# :userId => "foo", +# :type => "track", +# :event => "bar", +# :properties => {} +# } +# ] + +client.test_queue.track +# [ +# { +# :context => { +# :library => { +# :name => "analytics-ruby", +# :version => "2.2.8.pre" +# } +# }, +# :messageId => "e9754cc0-1c5e-47e4-832a-203589d279e4", +# :timestamp => "2021-02-19T13:32:39.547+01:00", +# :userId => "foo", +# :type => "track", +# :event => "bar", +# :properties => {} +# } +# ] + +# Other available methods +client.test_queue.alias # => [] +client.test_queue.group # => [] +client.test_queue.identify # => [] +client.test_queue.page # => [] +client.test_queue.screen # => [] + +client.reset! + +client.test_queue.all # => [] +``` + +Note: It is recommended to call `reset!` before each test to ensure your test queue is empty. For example, in rspec you may have the following: + +```ruby +RSpec.configure do |config| + config.before do + Analytics.test_queue.reset! + end +end +``` ## License diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index 1e47c62..ca83353 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -7,6 +7,7 @@ require 'segment/analytics/transport' require 'segment/analytics/response' require 'segment/analytics/logging' +require 'segment/analytics/test_queue' module Segment class Analytics diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 61ee386..fc81cce 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -21,6 +21,7 @@ def initialize(opts = {}) symbolize_keys!(opts) @queue = Queue.new + @test = opts[:test] @write_key = opts[:write_key] @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE @worker_mutex = Mutex.new @@ -143,6 +144,14 @@ def queued_messages @queue.length end + def test_queue + unless @test + raise 'Test queue only available when setting :test to true.' + end + + @test_queue ||= TestQueue.new + end + private # private: Enqueues the action. @@ -152,6 +161,8 @@ def enqueue(action) # add our request id for tracing purposes action[:messageId] ||= uid + test_queue << action if @test + if @queue.length < @max_queue_size @queue << action ensure_worker_running diff --git a/lib/segment/analytics/test_queue.rb b/lib/segment/analytics/test_queue.rb new file mode 100644 index 0000000..af31558 --- /dev/null +++ b/lib/segment/analytics/test_queue.rb @@ -0,0 +1,56 @@ +module Segment + class Analytics + class TestQueue + attr_reader :messages + + def initialize + reset! + end + + def [](key) + all[key] + end + + def count + all.count + end + + def <<(message) + all << message + send(message[:type]) << message + end + + def alias + messages[:alias] ||= [] + end + + def all + messages[:all] ||= [] + end + + def group + messages[:group] ||= [] + end + + def identify + messages[:identify] ||= [] + end + + def page + messages[:page] ||= [] + end + + def screen + messages[:screen] ||= [] + end + + def track + messages[:track] ||= [] + end + + def reset! + @messages = {} + end + end + end +end diff --git a/spec/segment/analytics/test_queue_spec.rb b/spec/segment/analytics/test_queue_spec.rb new file mode 100644 index 0000000..810a49e --- /dev/null +++ b/spec/segment/analytics/test_queue_spec.rb @@ -0,0 +1,213 @@ +require 'spec_helper' + +module Segment + class Analytics + describe TestQueue do + let(:test_queue) { described_class.new } + + describe '#initialize' do + it 'starts empty' do + expect(test_queue.messages).to eq({}) + end + end + + describe '#<<' do + let(:message) do + { + type: type, + foo: "bar" + } + end + + let(:expected_messages) do + { + type.to_sym => [message], + all: [message] + } + end + + context 'when unsupported type' do + let(:type) { :foo } + + it 'raises error' do + expect { test_queue << message }.to raise_error(NoMethodError) + end + end + + context 'when supported type' do + before do + test_queue << message + end + + context 'when type is alias' do + let(:type) { :alias } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to alias' do + expect(test_queue.alias).to eq([message]) + end + end + + context 'when type is group' do + let(:type) { :group } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to group' do + expect(test_queue.group).to eq([message]) + end + end + + context 'when type is identify' do + let(:type) { :identify } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to identify' do + expect(test_queue.identify).to eq([message]) + end + end + + context 'when type is page' do + let(:type) { :page } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to page' do + expect(test_queue.page).to eq([message]) + end + end + + context 'when type is screen' do + let(:type) { :screen } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to screen' do + expect(test_queue.screen).to eq([message]) + end + end + + context 'when type is track' do + let(:type) { :track } + + it 'adds messages' do + expect(test_queue.messages).to eq(expected_messages) + end + + it 'adds type to all' do + expect(test_queue.all).to eq([message]) + end + + it 'adds type to track' do + expect(test_queue.track).to eq([message]) + end + end + end + end + + describe '#count' do + let(:message) do + { + type: "alias", + foo: "bar" + } + end + + it "returns 0" do + expect(test_queue.count).to eq(0) + end + + it "returns 1" do + test_queue << message + expect(test_queue.count).to eq(1) + end + + it "returns 2" do + test_queue << message + test_queue << message + expect(test_queue.count).to eq(2) + end + end + + describe '#[]' do + let(:message1) do + { + type: "alias", + foo: "bar" + } + end + + let(:message2) do + { + type: "identify", + foo: "baz" + } + end + + it "returns message1" do + test_queue << message1 + expect(test_queue[0]).to eq(message1) + end + + it "returns message2" do + test_queue << message2 + expect(test_queue[0]).to eq(message2) + end + + it "returns message2" do + test_queue << message1 + test_queue << message2 + expect(test_queue[1]).to eq(message2) + end + end + + describe '#reset!' do + let(:message) do + { + type: "alias", + foo: "bar" + } + end + + it "returns message" do + test_queue << message + expect(test_queue.count).to eq(1) + test_queue.reset! + expect(test_queue.messages).to eq({}) + end + end + end + end +end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index f4444dd..17961b2 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -127,6 +127,33 @@ class Analytics end end end + + describe '#test_queue' do + context 'when not in mode' do + let(:analytics) { Segment::Analytics.new :write_key => WRITE_KEY, :stub => true, :test => true } + + it 'returns TestQueue' do + expect(analytics.test_queue).to be_a(TestQueue) + end + + it 'returns event' do + analytics.track Queued::TRACK + expect(analytics.test_queue[0]).to include(Requested::TRACK) + expect(analytics.test_queue.track[0]).to include(Requested::TRACK) + end + end + + context 'when not in test mode' do + let(:analytics) { Segment::Analytics.new :write_key => WRITE_KEY, :stub => true, :test => false } + + it 'errors when not in test mode' do + expect(analytics.instance_variable_get(:@test)).to be_falsey + expect { analytics.test_queue }.to raise_error( + RuntimeError, 'Test queue only available when setting :test to true.' + ) + end + end + end end end end From 337072c16aab3b2055bdfd75475c8fc254a60050 Mon Sep 17 00:00:00 2001 From: Ryan Jackson Date: Fri, 9 Apr 2021 15:49:17 +0200 Subject: [PATCH 262/322] Update quotes for rubocop --- README.md | 2 +- spec/segment/analytics/test_queue_spec.rb | 32 +++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f3b3d5a..f19f941 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ client = Segment::Analytics.new(test: true) client.test_queue # => # -client.track(user_id: "foo", event: "bar") +client.track(user_id: 'foo', event: 'bar') client.test_queue.all # [ diff --git a/spec/segment/analytics/test_queue_spec.rb b/spec/segment/analytics/test_queue_spec.rb index 810a49e..c97431d 100644 --- a/spec/segment/analytics/test_queue_spec.rb +++ b/spec/segment/analytics/test_queue_spec.rb @@ -15,7 +15,7 @@ class Analytics let(:message) do { type: type, - foo: "bar" + foo: 'bar' } end @@ -140,21 +140,21 @@ class Analytics describe '#count' do let(:message) do { - type: "alias", - foo: "bar" + type: 'alias', + foo: 'bar' } end - it "returns 0" do + it 'returns 0' do expect(test_queue.count).to eq(0) end - it "returns 1" do + it 'returns 1' do test_queue << message expect(test_queue.count).to eq(1) end - it "returns 2" do + it 'returns 2' do test_queue << message test_queue << message expect(test_queue.count).to eq(2) @@ -164,29 +164,29 @@ class Analytics describe '#[]' do let(:message1) do { - type: "alias", - foo: "bar" + type: 'alias', + foo: 'bar' } end let(:message2) do { - type: "identify", - foo: "baz" + type: 'identify', + foo: 'baz' } end - it "returns message1" do + it 'returns message1' do test_queue << message1 expect(test_queue[0]).to eq(message1) end - it "returns message2" do + it 'returns message2' do test_queue << message2 expect(test_queue[0]).to eq(message2) end - it "returns message2" do + it 'returns message2' do test_queue << message1 test_queue << message2 expect(test_queue[1]).to eq(message2) @@ -196,12 +196,12 @@ class Analytics describe '#reset!' do let(:message) do { - type: "alias", - foo: "bar" + type: 'alias', + foo: 'bar' } end - it "returns message" do + it 'returns message' do test_queue << message expect(test_queue.count).to eq(1) test_queue.reset! From 3ff9d0186dffbed1933ef30acbff792df5a4e804 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 13 Apr 2021 14:37:54 -0500 Subject: [PATCH 263/322] Update version for gem --- History.md | 6 ++++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 3d888f2..6732570 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +2.3.1 / 2020-04-13 +================== + + * Add test option for easier testing (https://github.com/segmentio/analytics-ruby/pull/222) + + 2.3.0 / 2020-03-26 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 62d2fa0..1191fc4 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.3.0' + VERSION = '2.3.1' end end From 98adba6ba2c1bc7e87f446c6b0d08d99e52f6bd7 Mon Sep 17 00:00:00 2001 From: annc128 Date: Wed, 28 Apr 2021 17:39:11 -0700 Subject: [PATCH 264/322] enable-overriding-transport-options Pass options when initializing Transport --- lib/segment/analytics/worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index a313eed..7e73fae 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -29,7 +29,7 @@ def initialize(queue, write_key, options = {}) batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE @batch = MessageBatch.new(batch_size) @lock = Mutex.new - @transport = Transport.new + @transport = Transport.new(options) end # public: Continuously runs the loop to check for new events From 156f7896e40ef76874b06d106b27093a01c32076 Mon Sep 17 00:00:00 2001 From: annc128 Date: Wed, 28 Apr 2021 18:18:17 -0700 Subject: [PATCH 265/322] enable-overriding-transport-options Bump version --- History.md | 5 +++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 6732570..774346f 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.3.2 / 2020-04-28 +================== + +* Enable overriding options in Transport () + 2.3.1 / 2020-04-13 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 1191fc4..3c375ed 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.3.1' + VERSION = '2.3.2' end end From 6bc8d8fb1037a5fec9418c1705e6d071e71bc693 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 5 May 2021 14:46:19 -0500 Subject: [PATCH 266/322] Version update 2.3.3 --- History.md | 5 +++++ lib/segment/analytics/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 774346f..736f01a 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.3.3 / 2020-05-05 +================== + +* Enable overriding transport Pass options when initializing Transport () + 2.3.2 / 2020-04-28 ================== diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 3c375ed..8ff9daa 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.3.2' + VERSION = '2.3.3' end end From 8fa2e1c92afc810d44da1993a85751be082667ed Mon Sep 17 00:00:00 2001 From: Pooya Jaferian Date: Wed, 5 May 2021 12:52:06 -0700 Subject: [PATCH 267/322] Update History.md --- History.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/History.md b/History.md index 736f01a..b3534db 100644 --- a/History.md +++ b/History.md @@ -1,12 +1,8 @@ -2.3.3 / 2020-05-05 +2.4.0 / 2020-05-05 ================== * Enable overriding transport Pass options when initializing Transport () -2.3.2 / 2020-04-28 -================== - -* Enable overriding options in Transport () 2.3.1 / 2020-04-13 ================== From 9b1dc2ff4c176b526d7fbe17328db501104cb2a3 Mon Sep 17 00:00:00 2001 From: Pooya Jaferian Date: Wed, 5 May 2021 12:53:36 -0700 Subject: [PATCH 268/322] Update version.rb --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 8ff9daa..199992a 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.3.3' + VERSION = '2.4.0' end end From a3c83a245aed6c661ecf8f4c3e4cafd02814a072 Mon Sep 17 00:00:00 2001 From: David Hughes Date: Wed, 9 Jun 2021 12:59:48 -0700 Subject: [PATCH 269/322] Fix test queue reset! documentation I ran into this with version 2.4.0, not sure if this is a just a typo or if the interface changed between versions. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f19f941..1cfebc0 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ client.test_queue.identify # => [] client.test_queue.page # => [] client.test_queue.screen # => [] -client.reset! +client.test_queue.reset! client.test_queue.all # => [] ``` From 9d230d97a11dfb58c04cce4cd40376e910b94545 Mon Sep 17 00:00:00 2001 From: Pooya Jaferian Date: Wed, 16 Jun 2021 11:09:56 -0700 Subject: [PATCH 270/322] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 1cfebc0..7d83122 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,6 @@ There are a few calls available, please check the documentation section. Documentation is available at [segment.com/docs/sources/server/ruby](https://segment.com/docs/sources/server/ruby/) -## Testing - -You can use the `stub: true` option to Segment::Analytics.new to cause all requests to be stubbed, making it easier to test with this library. - ### Test Queue You can use the `test: true` option to Segment::Analytics.new to cause all requests to be saved to a test queue until manually reset. All events will process as specified by the configuration, and they will also be stored in a separate queue for inspection during testing. From 675b296393d304aa3903737613f5dbb4e643591c Mon Sep 17 00:00:00 2001 From: MaxenceFlatlooker Date: Mon, 21 Jun 2021 16:38:31 +0200 Subject: [PATCH 271/322] not enqueuing test action in real queue --- lib/segment/analytics/client.rb | 9 +++++---- spec/segment/analytics/client_spec.rb | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index fc81cce..9e626d9 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -161,7 +161,10 @@ def enqueue(action) # add our request id for tracing purposes action[:messageId] ||= uid - test_queue << action if @test + if @test + test_queue << action + return true + end if @queue.length < @max_queue_size @queue << action @@ -170,9 +173,7 @@ def enqueue(action) true else logger.warn( - 'Queue is full, dropping events. The :max_queue_size ' \ - 'configuration parameter can be increased to prevent this from ' \ - 'happening.' + 'Queue is full, dropping events. The :max_queue_size configuration parameter can be increased to prevent this from happening.' ) false end diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index c88e7ab..0358bb2 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -342,6 +342,21 @@ class Analytics expect(message[:integrations][:Salesforce]).to eq(false) end end + + it 'does not enqueue the action in test mode' do + client.instance_variable_set(:@test, true) + client.test_queue + test_queue = client.instance_variable_get(:@test_queue) + + %i[track screen page group identify alias].each do |s| + old_test_queue_size = test_queue.count + queue_size = queue.length + client.send(s, data) + + expect(queue.length).to eq(queue_size) # The "real" queue size should not change in test mode + expect(test_queue.count).to_not eq(old_test_queue_size) # The "test" queue size should change in test mode + end + end end end end From 18a8408097319d01889de45d6a159317ce961936 Mon Sep 17 00:00:00 2001 From: Diego Silva <8826716+diego-silva@users.noreply.github.com> Date: Wed, 25 Aug 2021 17:54:04 -0300 Subject: [PATCH 272/322] Correct dates on History.md Versions 2.3.0 up to 2.4.0 were released in 2021, not in 2020. --- History.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index b3534db..6417a98 100644 --- a/History.md +++ b/History.md @@ -1,16 +1,16 @@ -2.4.0 / 2020-05-05 +2.4.0 / 2021-05-05 ================== * Enable overriding transport Pass options when initializing Transport () -2.3.1 / 2020-04-13 +2.3.1 / 2021-04-13 ================== * Add test option for easier testing (https://github.com/segmentio/analytics-ruby/pull/222) -2.3.0 / 2020-03-26 +2.3.0 / 2021-03-26 ================== * [Improvement](https://github.com/segmentio/analytics-ruby/pull/225): Update timestamp for sub-millisecond reporting From c908070c9da16f67747a74d5f762352c59a3616d Mon Sep 17 00:00:00 2001 From: Tyler Goodwin Date: Thu, 14 Oct 2021 09:18:57 +1100 Subject: [PATCH 273/322] Fix for empty user_id or anonymous_id not throwing errors Events were being swallowed if the user id was an empty string. --- lib/segment/analytics/field_parser.rb | 13 +++++++++---- spec/segment/analytics_spec.rb | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 7918710..56e2f12 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -160,9 +160,10 @@ def parse_common_fields(fields) end def check_user_id!(fields) - unless fields[:user_id] || fields[:anonymous_id] - raise ArgumentError, 'Must supply either user_id or anonymous_id' - end + return unless blank?(fields[:user_id]) + return unless blank?(fields[:anonymous_id]) + + raise ArgumentError, 'Must supply either user_id or anonymous_id' end def check_timestamp!(timestamp) @@ -178,11 +179,15 @@ def add_context!(context) # obj - String|Number that must be non-blank # name - Name of the validated value def check_presence!(obj, name) - if obj.nil? || (obj.is_a?(String) && obj.empty?) + if blank?(obj) raise ArgumentError, "#{name} must be given" end end + def blank?(obj) + obj.nil? || (obj.is_a?(String) && obj.empty?) + end + def check_is_hash!(obj, name) raise ArgumentError, "#{name} must be a Hash" unless obj.is_a? Hash end diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index 17961b2..7d20611 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -12,6 +12,8 @@ class Analytics it 'errors without user_id or anonymous_id' do expect { analytics.track :event => 'event' }.to raise_error(ArgumentError) + expect { analytics.track :event => 'event', user_id: ''}.to raise_error(ArgumentError) + expect { analytics.track :event => 'event', anonymous_id:''}.to raise_error(ArgumentError) expect { analytics.track :event => 'event', user_id: '1234' }.to_not raise_error(ArgumentError) expect { analytics.track :event => 'event', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end From 36f82d1618f4b0c6e202674bbcf4478ebe9c23ca Mon Sep 17 00:00:00 2001 From: "Shane L. Duvall" Date: Mon, 17 Jan 2022 14:43:17 -0600 Subject: [PATCH 274/322] Create ruby.yml --- .github/workflows/ruby.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/ruby.yml diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 0000000..2006e3f --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,35 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: Ruby + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: ['2.4.3', '2.5', '2.6', '2.7.3'] + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, + # change this to (see https://github.com/ruby/setup-ruby#versioning): + # uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Run tests + run: bundle exec rake From 5f4328e6b2b0d844b53c64ace53d1fe7de6282c4 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Mon, 17 Jan 2022 14:59:08 -0600 Subject: [PATCH 275/322] Update test version --- .github/workflows/ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 2006e3f..905aa5c 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.4.3', '2.5', '2.6', '2.7.3'] + ruby-version: ['2.4.3', '2.5', '2.6', '2.7.2'] steps: - uses: actions/checkout@v2 From 34a5be7950d0f6bd31939bb873f4360dcb063e18 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Mon, 17 Jan 2022 16:44:21 -0600 Subject: [PATCH 276/322] rubocop update parameters; fix syntax reported by rubocop --- .rubocop_todo.yml | 2 +- spec/segment/analytics_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 11a76cc..8024cb6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -13,7 +13,7 @@ Metrics/AbcSize: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 114 + Max: 120 # Offense count: 1 Metrics/CyclomaticComplexity: diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index 7d20611..bf4dbed 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -12,8 +12,8 @@ class Analytics it 'errors without user_id or anonymous_id' do expect { analytics.track :event => 'event' }.to raise_error(ArgumentError) - expect { analytics.track :event => 'event', user_id: ''}.to raise_error(ArgumentError) - expect { analytics.track :event => 'event', anonymous_id:''}.to raise_error(ArgumentError) + expect { analytics.track :event => 'event', user_id: '' }.to raise_error(ArgumentError) + expect { analytics.track :event => 'event', anonymous_id: '' }.to raise_error(ArgumentError) expect { analytics.track :event => 'event', user_id: '1234' }.to_not raise_error(ArgumentError) expect { analytics.track :event => 'event', anonymous_id: '2345' }.to_not raise_error(ArgumentError) end From c03164ba338f3593ca5b1b3f4043cb0e28366fb8 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Mon, 17 Jan 2022 16:51:29 -0600 Subject: [PATCH 277/322] rubocop syntax update --- lib/segment/analytics/field_parser.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 56e2f12..602f4bb 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -179,9 +179,7 @@ def add_context!(context) # obj - String|Number that must be non-blank # name - Name of the validated value def check_presence!(obj, name) - if blank?(obj) - raise ArgumentError, "#{name} must be given" - end + raise ArgumentError, "#{name} must be given" if blank?(obj) end def blank?(obj) From 48bac002f7d6a8638337e1c8da61fc37c3f00c1a Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Mon, 17 Jan 2022 19:53:25 -0600 Subject: [PATCH 278/322] Add ruby gems config --- .github/workflows/gem-push.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/gem-push.yml diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml new file mode 100644 index 0000000..8518638 --- /dev/null +++ b/.github/workflows/gem-push.yml @@ -0,0 +1,33 @@ +name: Ruby Gem + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: Build + Publish + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby 2.7 + uses: actions/setup-ruby@v1 + with: + ruby-version: 2.7.x + + - name: Publish to RubyGems + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem build *.gemspec + gem push *.gem + env: + GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}" From 5d53b6c7964ed803c45d34ff80dd801cfeda305e Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Mon, 17 Jan 2022 20:22:35 -0600 Subject: [PATCH 279/322] Add GPR into config --- .github/workflows/gem-push.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index 8518638..ead605f 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -21,6 +21,18 @@ jobs: with: ruby-version: 2.7.x + - name: Publish to GPR + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem build *.gemspec + gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem + env: + GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}" + OWNER: ${{ github.repository_owner }} + - name: Publish to RubyGems run: | mkdir -p $HOME/.gem From 9e61564f0557850a695735847024aee8ba37b27e Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 23 Feb 2022 13:36:55 -0600 Subject: [PATCH 280/322] Resolve Issue #247, change milliseconds from reporting 6 places back to 3 --- lib/segment/analytics/utils.rb | 2 +- lib/segment/analytics/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 745a5e4..2c2d86a 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -65,7 +65,7 @@ def datetime_in_iso8601(datetime) end def time_in_iso8601(time) - "#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}" + "#{time.strftime('%Y-%m-%dT%H:%M:%S.%3N')}#{formatted_offset(time, true, 'Z')}" end def date_in_iso8601(date) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 199992a..43460ff 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.4.0' + VERSION = '2.4.1' end end From d188a3943d05b8f0ff655f0c7eb0cdd3c1a60555 Mon Sep 17 00:00:00 2001 From: Raghu Bhupatiraju Date: Wed, 20 Apr 2022 13:59:54 -0700 Subject: [PATCH 281/322] Mentioning about stubbing during tests. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7d83122..e6cdb6b 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ RSpec.configure do |config| end ``` +And also to stub actions use `stub: true` along with `test: true` so that it doesn't send any real calls during specs. ## License ``` From d6d9a33e30cf396b597315372e8f2ce8adf49400 Mon Sep 17 00:00:00 2001 From: Raghu Bhupatiraju Date: Wed, 20 Apr 2022 16:23:52 -0700 Subject: [PATCH 282/322] Bumping the version --- lib/segment/analytics/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index 43460ff..c292749 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,5 +1,5 @@ module Segment class Analytics - VERSION = '2.4.1' + VERSION = '2.4.2' end end From 71b30778a0be495154ebd5bd5efd515f1c232fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Rebughini?= Date: Tue, 10 Jan 2023 15:01:18 +0100 Subject: [PATCH 283/322] Add frozen_string_literals magic comment This low effort change greatly changes the outcome of how many strings are allocated and, most importantly, retained by the gem. --- lib/analytics-ruby.rb | 2 ++ lib/segment.rb | 2 ++ lib/segment/analytics.rb | 2 ++ lib/segment/analytics/backoff_policy.rb | 2 ++ lib/segment/analytics/client.rb | 2 ++ lib/segment/analytics/defaults.rb | 2 ++ lib/segment/analytics/field_parser.rb | 2 ++ lib/segment/analytics/logging.rb | 2 ++ lib/segment/analytics/message_batch.rb | 2 ++ lib/segment/analytics/response.rb | 2 ++ lib/segment/analytics/test_queue.rb | 2 ++ lib/segment/analytics/transport.rb | 2 ++ lib/segment/analytics/utils.rb | 2 ++ lib/segment/analytics/version.rb | 2 ++ lib/segment/analytics/worker.rb | 2 ++ spec/isolated/json_example.rb | 2 ++ spec/isolated/with_active_support.rb | 2 ++ spec/isolated/with_active_support_and_oj.rb | 2 ++ spec/isolated/with_oj.rb | 2 ++ spec/segment/analytics/backoff_policy_spec.rb | 2 ++ spec/segment/analytics/client_spec.rb | 2 ++ spec/segment/analytics/message_batch_spec.rb | 2 ++ spec/segment/analytics/response_spec.rb | 2 ++ spec/segment/analytics/test_queue_spec.rb | 2 ++ spec/segment/analytics/transport_spec.rb | 2 ++ spec/segment/analytics/worker_spec.rb | 2 ++ spec/segment/analytics_spec.rb | 2 ++ spec/spec_helper.rb | 2 ++ 28 files changed, 56 insertions(+) diff --git a/lib/analytics-ruby.rb b/lib/analytics-ruby.rb index 801ad01..1c99ea1 100644 --- a/lib/analytics-ruby.rb +++ b/lib/analytics-ruby.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + require 'segment' diff --git a/lib/segment.rb b/lib/segment.rb index 1465165..0efdd70 100644 --- a/lib/segment.rb +++ b/lib/segment.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + require 'segment/analytics' diff --git a/lib/segment/analytics.rb b/lib/segment/analytics.rb index ca83353..707e7c4 100644 --- a/lib/segment/analytics.rb +++ b/lib/segment/analytics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'segment/analytics/version' require 'segment/analytics/defaults' require 'segment/analytics/utils' diff --git a/lib/segment/analytics/backoff_policy.rb b/lib/segment/analytics/backoff_policy.rb index 394a2c1..e6033b1 100644 --- a/lib/segment/analytics/backoff_policy.rb +++ b/lib/segment/analytics/backoff_policy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'segment/analytics/defaults' module Segment diff --git a/lib/segment/analytics/client.rb b/lib/segment/analytics/client.rb index 9e626d9..9589f69 100644 --- a/lib/segment/analytics/client.rb +++ b/lib/segment/analytics/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'thread' require 'time' diff --git a/lib/segment/analytics/defaults.rb b/lib/segment/analytics/defaults.rb index 8562346..aa32697 100644 --- a/lib/segment/analytics/defaults.rb +++ b/lib/segment/analytics/defaults.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Segment class Analytics module Defaults diff --git a/lib/segment/analytics/field_parser.rb b/lib/segment/analytics/field_parser.rb index 602f4bb..a7364ec 100644 --- a/lib/segment/analytics/field_parser.rb +++ b/lib/segment/analytics/field_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Segment class Analytics # Handles parsing fields according to the Segment Spec diff --git a/lib/segment/analytics/logging.rb b/lib/segment/analytics/logging.rb index f7aaa63..5449878 100644 --- a/lib/segment/analytics/logging.rb +++ b/lib/segment/analytics/logging.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'logger' module Segment diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 1522463..15eef43 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'forwardable' require 'segment/analytics/logging' diff --git a/lib/segment/analytics/response.rb b/lib/segment/analytics/response.rb index 7306ac0..c31116a 100644 --- a/lib/segment/analytics/response.rb +++ b/lib/segment/analytics/response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Segment class Analytics class Response diff --git a/lib/segment/analytics/test_queue.rb b/lib/segment/analytics/test_queue.rb index af31558..c93cad2 100644 --- a/lib/segment/analytics/test_queue.rb +++ b/lib/segment/analytics/test_queue.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Segment class Analytics class TestQueue diff --git a/lib/segment/analytics/transport.rb b/lib/segment/analytics/transport.rb index 59697be..6ee14d8 100644 --- a/lib/segment/analytics/transport.rb +++ b/lib/segment/analytics/transport.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'segment/analytics/defaults' require 'segment/analytics/utils' require 'segment/analytics/response' diff --git a/lib/segment/analytics/utils.rb b/lib/segment/analytics/utils.rb index 2c2d86a..62ee69b 100644 --- a/lib/segment/analytics/utils.rb +++ b/lib/segment/analytics/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' module Segment diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index c292749..eaabbdf 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Segment class Analytics VERSION = '2.4.2' diff --git a/lib/segment/analytics/worker.rb b/lib/segment/analytics/worker.rb index 7e73fae..6a7d68e 100644 --- a/lib/segment/analytics/worker.rb +++ b/lib/segment/analytics/worker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'segment/analytics/defaults' require 'segment/analytics/message_batch' require 'segment/analytics/transport' diff --git a/spec/isolated/json_example.rb b/spec/isolated/json_example.rb index c693b55..5f80533 100644 --- a/spec/isolated/json_example.rb +++ b/spec/isolated/json_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'message_batch_json' do it 'MessageBatch generates proper JSON' do batch = Segment::Analytics::MessageBatch.new(100) diff --git a/spec/isolated/with_active_support.rb b/spec/isolated/with_active_support.rb index 86178ae..cfd9f2c 100644 --- a/spec/isolated/with_active_support.rb +++ b/spec/isolated/with_active_support.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'isolated/json_example' diff --git a/spec/isolated/with_active_support_and_oj.rb b/spec/isolated/with_active_support_and_oj.rb index 18724e3..135be31 100644 --- a/spec/isolated/with_active_support_and_oj.rb +++ b/spec/isolated/with_active_support_and_oj.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'isolated/json_example' diff --git a/spec/isolated/with_oj.rb b/spec/isolated/with_oj.rb index ad3f376..7519238 100644 --- a/spec/isolated/with_oj.rb +++ b/spec/isolated/with_oj.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'isolated/json_example' diff --git a/spec/segment/analytics/backoff_policy_spec.rb b/spec/segment/analytics/backoff_policy_spec.rb index 75dc9fc..25ef05e 100644 --- a/spec/segment/analytics/backoff_policy_spec.rb +++ b/spec/segment/analytics/backoff_policy_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/client_spec.rb b/spec/segment/analytics/client_spec.rb index 0358bb2..99c973b 100644 --- a/spec/segment/analytics/client_spec.rb +++ b/spec/segment/analytics/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 4b7b66f..98f7be7 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/response_spec.rb b/spec/segment/analytics/response_spec.rb index 7e09971..bb673db 100644 --- a/spec/segment/analytics/response_spec.rb +++ b/spec/segment/analytics/response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/test_queue_spec.rb b/spec/segment/analytics/test_queue_spec.rb index c97431d..4aa4f77 100644 --- a/spec/segment/analytics/test_queue_spec.rb +++ b/spec/segment/analytics/test_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/transport_spec.rb b/spec/segment/analytics/transport_spec.rb index 56b551f..b73ce9d 100644 --- a/spec/segment/analytics/transport_spec.rb +++ b/spec/segment/analytics/transport_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics/worker_spec.rb b/spec/segment/analytics/worker_spec.rb index 0ca4d49..5111943 100644 --- a/spec/segment/analytics/worker_spec.rb +++ b/spec/segment/analytics/worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/segment/analytics_spec.rb b/spec/segment/analytics_spec.rb index bf4dbed..04431de 100644 --- a/spec/segment/analytics_spec.rb +++ b/spec/segment/analytics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Segment diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a0f9247..b1d8435 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # https://github.com/codecov/codecov-ruby#usage require 'simplecov' SimpleCov.start From 0484c52f9e7bfe4966f8029e6f0ecac78fe65e3c Mon Sep 17 00:00:00 2001 From: Orien Madgwick <497874+orien@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:13:37 +1100 Subject: [PATCH 284/322] CI: add Ruby 3.0, 3.1, and 3.2 to the test matrix --- .github/workflows/ruby.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 905aa5c..02d2de1 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,15 +19,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.4.3', '2.5', '2.6', '2.7.2'] + ruby-version: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby - # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, - # change this to (see https://github.com/ruby/setup-ruby#versioning): - # uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically From f18e6491439aef0c37d3c6db637954430e6184d9 Mon Sep 17 00:00:00 2001 From: Orien Madgwick <497874+orien@users.noreply.github.com> Date: Tue, 24 Jan 2023 18:44:24 +1100 Subject: [PATCH 285/322] Update development dependencies --- analytics-ruby.gemspec | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index 60d9155..aad3553 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -19,15 +19,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'commander', '~> 4.4' # Used in specs - spec.add_development_dependency 'rake', '~> 10.3' + spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.0' - spec.add_development_dependency 'tzinfo', '1.2.1' - spec.add_development_dependency 'activesupport', '~> 4.1.11' - if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java' + spec.add_development_dependency 'tzinfo', '~> 1.2' + spec.add_development_dependency 'activesupport', '~> 5.2.0' + if RUBY_PLATFORM != 'java' spec.add_development_dependency 'oj', '~> 3.6.2' end - if RUBY_VERSION >= '2.1' - spec.add_development_dependency 'rubocop', '~> 0.51.0' - end - spec.add_development_dependency 'codecov', '~> 0.1.4' + spec.add_development_dependency 'rubocop', '~> 1.0' + spec.add_development_dependency 'codecov', '~> 0.6' end From d258c1cb27f9892c9d2feab82f8a443a124e5ef4 Mon Sep 17 00:00:00 2001 From: Orien Madgwick <497874+orien@users.noreply.github.com> Date: Tue, 24 Jan 2023 19:31:43 +1100 Subject: [PATCH 286/322] Rubocop: resolve Style/HashSyntax --- bin/analytics | 72 +++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/bin/analytics b/bin/analytics index 86f3511..1f4c19e 100755 --- a/bin/analytics +++ b/bin/analytics @@ -43,62 +43,62 @@ command :send do |c| c.action do |args, options| Analytics = Segment::Analytics.new({ - write_key: options.writeKey, - on_error: Proc.new { |status, msg| print msg } + :write_key => options.writeKey, + :on_error => Proc.new { |status, msg| print msg } }) case options.type when "track" Analytics.track({ - user_id: options.userId, - event: options.event, - anonymous_id: options.anonymousId, - properties: json_hash(options.properties), - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :user_id => options.userId, + :event => options.event, + :anonymous_id => options.anonymousId, + :properties => json_hash(options.properties), + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) when "page" Analytics.page({ - user_id: options.userId, - anonymous_id: options.anonymousId, - name: options.name, - properties: json_hash(options.properties), - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :user_id => options.userId, + :anonymous_id => options.anonymousId, + :name => options.name, + :properties => json_hash(options.properties), + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) when "screen" Analytics.screen({ - user_id: options.userId, - anonymous_id: options.anonymousId, - name: options.name, - properties: json_hash(options.properties), - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :user_id => options.userId, + :anonymous_id => options.anonymousId, + :name => options.name, + :properties => json_hash(options.properties), + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) when "identify" Analytics.identify({ - user_id: options.userId, - anonymous_id: options.anonymousId, - traits: json_hash(options.traits), - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :user_id => options.userId, + :anonymous_id => options.anonymousId, + :traits => json_hash(options.traits), + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) when "group" Analytics.group({ - user_id: options.userId, - anonymous_id: options.anonymousId, - group_id: options.groupId, - traits: json_hash(options.traits), - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :user_id => options.userId, + :anonymous_id => options.anonymousId, + :group_id => options.groupId, + :traits => json_hash(options.traits), + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) when "alias" Analytics.alias({ - previous_id: options.previousId, - user_id: options.userId, - anonymous_id: options.anonymousId, - context: json_hash(options.context), - integrations: json_hash(options.integrations) + :previous_id => options.previousId, + :user_id => options.userId, + :anonymous_id => options.anonymousId, + :context => json_hash(options.context), + :integrations => json_hash(options.integrations) }) else raise "Invalid Message Type #{options.type}" From 32e92e68b54fe3b69828c1eb9cdeabcedbcce8c8 Mon Sep 17 00:00:00 2001 From: Orien Madgwick <497874+orien@users.noreply.github.com> Date: Tue, 24 Jan 2023 20:08:10 +1100 Subject: [PATCH 287/322] Rubocop: update configuration --- .rubocop.yml | 14 ++-- .rubocop_todo.yml | 165 +++++++++++++++++++++++++++++++++++++++++----- Rakefile | 5 +- 3 files changed, 159 insertions(+), 25 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a043e4f..ecabcc4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,10 +1,11 @@ inherit_from: .rubocop_todo.yml AllCops: - # Rubocop doesn't support 2.0, so we'll use the minimum available - TargetRubyVersion: 2.1 + TargetRubyVersion: '2.0' + SuggestExtensions: false + NewCops: disable -Layout/IndentHash: +Layout/FirstHashElementIndentation: EnforcedStyle: consistent Metrics/AbcSize: @@ -23,7 +24,7 @@ Metrics/CyclomaticComplexity: Exclude: - "spec/**/*.rb" -Metrics/LineLength: +Layout/LineLength: Exclude: - "spec/**/*.rb" @@ -40,16 +41,13 @@ Naming/FileName: - lib/analytics-ruby.rb # Gem name, added for easier Gemfile usage Naming/PredicateName: - NameWhitelist: + AllowedMethods: - is_requesting? # Can't be renamed, backwards compatibility Style/BlockDelimiters: Exclude: - 'spec/**/*' -Style/BracesAroundHashParameters: - Enabled: false - Style/DateTime: Exclude: - 'spec/**/*.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8024cb6..c02e129 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,35 +1,170 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-10-06 02:55:44 +0530 using RuboCop version 0.51.0. +# on 2023-01-24 09:12:04 UTC using RuboCop version 1.44.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. +# Include: **/*.gemspec +Gemspec/OrderedDependencies: + Exclude: + - 'analytics-ruby.gemspec' + +# Offense count: 1 +# Configuration parameters: Severity, Include. +# Include: **/*.gemspec +Gemspec/RubyVersionGlobalsUsage: + Exclude: + - 'analytics-ruby.gemspec' + # Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/segment/analytics/client.rb' + - 'spec/spec_helper.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'spec/segment/analytics/client_spec.rb' + - 'spec/spec_helper.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterComma: + Exclude: + - 'Rakefile' + +# Offense count: 1 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'bin/analytics' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'bin/analytics' + +# Offense count: 3 +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 24 + Max: 25 + +# Offense count: 3 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. +# AllowedMethods: refine +Metrics/BlockLength: + Max: 76 # Offense count: 1 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 120 + Max: 115 -# Offense count: 1 +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods. Metrics/CyclomaticComplexity: Max: 8 -# Offense count: 8 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 147 - -# Offense count: 10 -# Configuration parameters: CountComments. +# Offense count: 11 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods. Metrics/MethodLength: Max: 16 # Offense count: 1 -Metrics/PerceivedComplexity: - Max: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: + Exclude: + - 'spec/spec_helper.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/ExpandPathArguments: + Exclude: + - 'analytics-ruby.gemspec' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns, IgnoredMethods. +# SupportedStyles: annotated, template, unannotated +Style/FormatStringToken: + EnforcedStyle: unannotated + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/GlobalStdStream: + Exclude: + - 'lib/segment/analytics/logging.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Exclude: + - 'analytics-ruby.gemspec' + - 'bin/analytics' + - 'lib/segment/analytics/client.rb' + +# Offense count: 1 +Style/MixinUsage: + Exclude: + - 'spec/spec_helper.rb' + +# Offense count: 2 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/segment/analytics/utils.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/Proc: + Exclude: + - 'bin/analytics' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'bin/analytics' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'Rakefile' + - 'analytics-ruby.gemspec' + - 'bin/analytics' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'Rakefile' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 147 diff --git a/Rakefile b/Rakefile index 0d3f11a..bb22a9f 100644 --- a/Rakefile +++ b/Rakefile @@ -18,8 +18,9 @@ Dir.glob('spec/isolated/**/*.rb').each do |isolated_test_path| default_tasks << isolated_test_path end -# Rubocop doesn't support < 2.1 -if RUBY_VERSION >= "2.1" +# Older versions of Rubocop don't support a target Ruby version of 2.1 +require 'rubocop/version' +if RuboCop::Version::STRING >= '1.30.0' require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) do |task| From 58334031e9bc6556c81569857148e590ad09b275 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Thu, 2 Mar 2023 16:04:29 -0600 Subject: [PATCH 288/322] Updating sub-version --- .github/workflows/ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 02d2de1..91f608a 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2'] + ruby-version: ['2.4', '2.5', '2.6', '2.7.2', '3.0', '3.1', '3.2'] steps: - uses: actions/checkout@v3 From c7f9e2dc27832ec14067b29d14103e8afcede0a1 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Thu, 2 Mar 2023 16:40:43 -0600 Subject: [PATCH 289/322] Update ruby config --- .github/workflows/gem-push.yml | 4 ++-- .github/workflows/ruby.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index ead605f..f118289 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -15,11 +15,11 @@ jobs: packages: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Ruby 2.7 uses: actions/setup-ruby@v1 with: - ruby-version: 2.7.x + ruby-version: 2.7.6 - name: Publish to GPR run: | diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 91f608a..02d2de1 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.4', '2.5', '2.6', '2.7.2', '3.0', '3.1', '3.2'] + ruby-version: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2'] steps: - uses: actions/checkout@v3 From abaeb57636073fd1698bcc3711619731b98fd2c0 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Thu, 2 Mar 2023 16:46:41 -0600 Subject: [PATCH 290/322] Update gem yaml --- .github/workflows/gem-push.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index f118289..60a8145 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Ruby 2.7 - uses: actions/setup-ruby@v1 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.6 + ruby-version: 2.7.x - name: Publish to GPR run: | From 13a0ddcef3db6c7e461c4289f977598ebea7c773 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Thu, 2 Mar 2023 16:47:47 -0600 Subject: [PATCH 291/322] Corrected Version --- .github/workflows/gem-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index 60a8145..f82bf12 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.x + ruby-version: 2.7.7 - name: Publish to GPR run: | From e9890bab9346acca9635cd650253fd8e0e510713 Mon Sep 17 00:00:00 2001 From: Aaron B <97627964+bockets@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:56 -0500 Subject: [PATCH 292/322] Update README.md The write key is required even if using a test queue. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6cdb6b..bc35023 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ You can use the `test: true` option to Segment::Analytics.new to cause all reque A test queue can be used as follows: ```ruby -client = Segment::Analytics.new(test: true) +client = Segment::Analytics.new(write_key: 'YOUR_WRITE_KEY', test: true) client.test_queue # => # From c440393016b13c39716610143a1c309668e48d68 Mon Sep 17 00:00:00 2001 From: Alan Charles <50601149+alanjcharles@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:48:34 -0700 Subject: [PATCH 293/322] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index bc35023..ab3eeb4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ analytics-ruby analytics-ruby is a ruby client for [Segment](https://segment.com) +### ⚠️ Maintenance ⚠️ +This library is in maintenance mode. It will send data as intended, but receive no new feature support and only critical maintenance updates from Segment. +

You can't fix what you can't measure

From e31e74293572c60deac5da6fdd6150374758a7db Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 17 Apr 2024 10:53:38 -0500 Subject: [PATCH 294/322] Push raising error message --- lib/segment/analytics/message_batch.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 15eef43..19700ac 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -29,6 +29,7 @@ def <<(message) message_json_size = message_json.bytesize if message_too_big?(message_json_size) logger.error('a message exceeded the maximum allowed size') + raise JSONGenerationError, "Message Exceeded Maximum Allowed Size" else @messages << message @json_size += message_json_size + 1 # One byte for the comma From d3cb44a800defbd376b74a7357dbac6c47f2cb2d Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:00:12 -0700 Subject: [PATCH 295/322] Update codecov token --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codecov.yml b/codecov.yml index c6e5dff..8c43af9 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,4 @@ ignore: - "spec/**/*" +codecov: +token: ${{ secrets.CODECOV_TOKEN }} From 89fe8e20c7284174d0d89ae43ba632fb69c991bd Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:03:06 -0700 Subject: [PATCH 296/322] Trying another format --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 8c43af9..2af45be 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,4 @@ ignore: - "spec/**/*" codecov: -token: ${{ secrets.CODECOV_TOKEN }} +token: ${ secrets.CODECOV_TOKEN } From 18fc214d1b87fd46e1e1074f4bdbfd22b8151c9f Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:13:50 -0700 Subject: [PATCH 297/322] Add codecov gh action to replace per-test upload --- .github/workflows/ruby.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 02d2de1..e602836 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -30,3 +30,7 @@ jobs: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run tests run: bundle exec rake + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.2.0 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From c1b795126dd8edfb6594b82036dc75c467b3e32f Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:18:46 -0700 Subject: [PATCH 298/322] Remove deprecated codecov uploader --- spec/spec_helper.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b1d8435..d4ebab3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,11 +1,5 @@ # frozen_string_literal: true -# https://github.com/codecov/codecov-ruby#usage -require 'simplecov' -SimpleCov.start -require 'codecov' -SimpleCov.formatter = SimpleCov::Formatter::Codecov - require 'segment/analytics' require 'active_support/time' From 08e2049b48e278a0ffada1e99856982e013a3e4a Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:19:48 -0700 Subject: [PATCH 299/322] Removing codecov dependency, using GH actions instead --- analytics-ruby.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/analytics-ruby.gemspec b/analytics-ruby.gemspec index aad3553..b45b31d 100644 --- a/analytics-ruby.gemspec +++ b/analytics-ruby.gemspec @@ -27,5 +27,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'oj', '~> 3.6.2' end spec.add_development_dependency 'rubocop', '~> 1.0' - spec.add_development_dependency 'codecov', '~> 0.6' end From a77c1f30f330f8844a9f4c46dac71b78e5883d72 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:40:08 -0700 Subject: [PATCH 300/322] Adding new codecov depenencies --- Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 817f62a..d54fb9a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,5 @@ source 'http://rubygems.org' gemspec + +gem 'simplecov' +gem 'simplecov-cobertura' From a0f577f55f14b34a0482d6553d43ea55efde1915 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:43:02 -0700 Subject: [PATCH 301/322] Adding new cov formatter --- spec/spec_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d4ebab3..20f7799 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +require "simplecov" +SimpleCov.start +require 'simplecov-cobertura' +SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter + require 'segment/analytics' require 'active_support/time' From fbe081bfe1270b9ce840562b6ff398467ad5d1d1 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:46:11 -0700 Subject: [PATCH 302/322] Fixing nit from codecov's tutorial code --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 20f7799..8dc8634 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "simplecov" +require 'simplecov' SimpleCov.start require 'simplecov-cobertura' SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter From 42d4dd8381d349879dc43e9d2ef0b2824a1807b7 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 1 May 2024 14:50:01 -0700 Subject: [PATCH 303/322] Codecov says commit yaml was invalid, reverting? --- codecov.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index 2af45be..c6e5dff 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,2 @@ ignore: - "spec/**/*" -codecov: -token: ${ secrets.CODECOV_TOKEN } From 91548dbca59c70ce0707bdb47af3194ecadd0a73 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 17 Apr 2024 10:53:38 -0500 Subject: [PATCH 304/322] Push raising error message --- lib/segment/analytics/message_batch.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 15eef43..19700ac 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -29,6 +29,7 @@ def <<(message) message_json_size = message_json.bytesize if message_too_big?(message_json_size) logger.error('a message exceeded the maximum allowed size') + raise JSONGenerationError, "Message Exceeded Maximum Allowed Size" else @messages << message @json_size += message_json_size + 1 # One byte for the comma From 8efb70764a030b0a8b45c925ed47e1fe7e5d0f60 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 17 May 2024 14:17:16 -0700 Subject: [PATCH 305/322] Export github issues to jira --- .github/workflows/create_jira.yml | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/create_jira.yml diff --git a/.github/workflows/create_jira.yml b/.github/workflows/create_jira.yml new file mode 100644 index 0000000..8180ac0 --- /dev/null +++ b/.github/workflows/create_jira.yml @@ -0,0 +1,39 @@ +name: Create Jira Ticket + +on: + issues: + types: + - opened + +jobs: + create_jira: + name: Create Jira Ticket + runs-on: ubuntu-latest + environment: IssueTracker + steps: + - name: Checkout + uses: actions/checkout@master + - name: Login + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }} + JIRA_EPIC_KEY: ${{ secrets.JIRA_EPIC_KEY }} + JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} + + - name: Create + id: create + uses: atlassian/gajira-create@master + with: + project: ${{ secrets.JIRA_PROJECT }} + issuetype: Bug + summary: | + [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }} + description: | + Github Link: ${{ github.event.issue.html_url }} + ${{ github.event.issue.body }} + fields: '{"parent": {"key": "${{ secrets.JIRA_EPIC_KEY }}"}}' + + - name: Log created issue + run: echo "Issue ${{ steps.create.outputs.issue }} was created" \ No newline at end of file From d993c8e341fe18ff3be27fa9320b5a8f88ffa70d Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 09:52:09 -0500 Subject: [PATCH 306/322] Resolve test response issue --- .github/workflows/gem-push.yml | 2 +- spec/segment/analytics/message_batch_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index f82bf12..ec95c5f 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -15,7 +15,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 98f7be7..0cba732 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,6 +19,8 @@ class Analytics subject << message expect(subject.length).to eq(0) + + expect { subject << message }.to raise_error("Message Exceeded Maximum Allowed Size") end end From ce515b2f6ae0c1d77484cfa91ccc77c94d39d5d8 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 09:59:44 -0500 Subject: [PATCH 307/322] update --- spec/segment/analytics/message_batch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 0cba732..8057a78 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -20,7 +20,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { subject << message }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { message }.to raise_error("Message Exceeded Maximum Allowed Size") end end From aded43ddec2493fb4d9e0dbb0f9a24828fa18362 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:05:58 -0500 Subject: [PATCH 308/322] update --- .github/workflows/ruby.yml | 2 +- spec/segment/analytics/message_batch_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index e602836..99831cc 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -22,7 +22,7 @@ jobs: ruby-version: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 8057a78..dac883c 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -20,7 +20,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { message }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { MessageBatch }.to raise_error("Message Exceeded Maximum Allowed Size") end end From 2440e09135ee8b2b631d398c5abda7e9af4c766a Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:09:49 -0500 Subject: [PATCH 309/322] update --- spec/segment/analytics/message_batch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index dac883c..0cba732 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -20,7 +20,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { MessageBatch }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { subject << message }.to raise_error("Message Exceeded Maximum Allowed Size") end end From 9b190ddf49fc8b7e6d70d099998faeb91ee7ef72 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:22:14 -0500 Subject: [PATCH 310/322] update --- spec/segment/analytics/message_batch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 0cba732..57ef715 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -20,7 +20,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { subject << message }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { MessageBatch.new }.to raise_error("Message Exceeded Maximum Allowed Size") end end From 44c94ce2c9778566423a577f5c2f8e9dba0c0feb Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:24:44 -0500 Subject: [PATCH 311/322] upd --- spec/segment/analytics/message_batch_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 57ef715..35c988a 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,7 +19,9 @@ class Analytics subject << message expect(subject.length).to eq(0) + end + it 'raises messages that exceed the maximum allowed size' do expect { MessageBatch.new }.to raise_error("Message Exceeded Maximum Allowed Size") end end From bad6777c40f5e204abf3be877d50ad0d7081990f Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:27:55 -0500 Subject: [PATCH 312/322] upd --- spec/segment/analytics/message_batch_spec.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 35c988a..efdadd1 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,10 +19,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - end - - it 'raises messages that exceed the maximum allowed size' do - expect { MessageBatch.new }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { Message }.to raise_error("Message Exceeded Maximum Allowed Size") end end From b4506dd56b5e8b17527ca32b5e498864e2130d38 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:30:00 -0500 Subject: [PATCH 313/322] upd --- spec/segment/analytics/message_batch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index efdadd1..73ff7f2 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,7 +19,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { Message }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { subject }.to raise_error("Message Exceeded Maximum Allowed Size") end end From 78bf82e239550f474d8846a093d20cfda2df0039 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:53:04 -0500 Subject: [PATCH 314/322] upd --- spec/segment/analytics/message_batch_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 73ff7f2..ccaf08d 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,7 +19,11 @@ class Analytics subject << message expect(subject.length).to eq(0) - expect { subject }.to raise_error("Message Exceeded Maximum Allowed Size") + + end + + it 'raises' do + expect { MessageBatch }.to raise_error("Message Exceeded Maximum Allowed Size") end end From f26d642bc8cec258c678d8aeab8d9051d7b19f99 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 10:59:16 -0500 Subject: [PATCH 315/322] test --- spec/segment/analytics/message_batch_spec.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index ccaf08d..d3e28a4 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -19,11 +19,7 @@ class Analytics subject << message expect(subject.length).to eq(0) - - end - - it 'raises' do - expect { MessageBatch }.to raise_error("Message Exceeded Maximum Allowed Size") + expect { raise StandardError }.to raise_error("Message Exceeded Maximum Allowed Size") end end From 91a24a5dea8e7d64b343aab4cac02f39d8abfee0 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 11:06:04 -0500 Subject: [PATCH 316/322] updt --- spec/segment/analytics/message_batch_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index d3e28a4..5f7b2fd 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -18,8 +18,7 @@ class Analytics message = { 'a' => 'b' * max_bytes } subject << message - expect(subject.length).to eq(0) - expect { raise StandardError }.to raise_error("Message Exceeded Maximum Allowed Size") + expect(subject.length).to eq(0).and_raise(JSONGenerationError.new("Message Exceeded Maximum Allowed Size")) end end From 6ede327a8a11ead4dd4acd864182c8ed41e86ec4 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 14:06:17 -0500 Subject: [PATCH 317/322] Final Adjustment --- spec/segment/analytics/message_batch_spec.rb | 33 +++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 5f7b2fd..8dd5370 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -1,38 +1,43 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" module Segment class Analytics describe MessageBatch do subject { described_class.new(100) } - describe '#<<' do - it 'appends messages' do - subject << { 'a' => 'b' } + describe "#<<" do + it "appends messages" do + expect(subject.length).to eq(0) + + subject << {"a" => "b"} + expect(subject.length).to eq(1) end - it 'rejects messages that exceed the maximum allowed size' do + it "rejects messages that exceed the maximum allowed size" do max_bytes = Defaults::Message::MAX_BYTES - message = { 'a' => 'b' * max_bytes } + message = {"a" => "b" * max_bytes} + + expect(subject.length).to eq(0) - subject << message - expect(subject.length).to eq(0).and_raise(JSONGenerationError.new("Message Exceeded Maximum Allowed Size")) + expect { subject << message }.to raise_error(MessageBatch::JSONGenerationError) + .with_message("Message Exceeded Maximum Allowed Size") end end - describe '#full?' do - it 'returns true once item count is exceeded' do - 99.times { subject << { a: 'b' } } + describe "#full?" do + it "returns true once item count is exceeded" do + 99.times { subject << {a: "b"} } expect(subject.full?).to be(false) - subject << { a: 'b' } + subject << {a: "b"} expect(subject.full?).to be(true) end - it 'returns true once max size is almost exceeded' do - message = { a: 'b' * (Defaults::Message::MAX_BYTES - 10) } + it "returns true once max size is almost exceeded" do + message = {a: "b" * (Defaults::Message::MAX_BYTES - 10)} message_size = message.to_json.bytesize From bdf9d9d5d50e77d6a7c533e7ba3cbca6385941fb Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 14:11:07 -0500 Subject: [PATCH 318/322] Fix auto correct quotes and spaces --- spec/segment/analytics/message_batch_spec.rb | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 8dd5370..0666dc5 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -1,43 +1,43 @@ # frozen_string_literal: true -require "spec_helper" +require 'spec_helper' module Segment class Analytics describe MessageBatch do subject { described_class.new(100) } - describe "#<<" do - it "appends messages" do + describe '#<<' do + it 'appends messages' do expect(subject.length).to eq(0) - subject << {"a" => "b"} + subject << { 'a' => 'b' } expect(subject.length).to eq(1) end - it "rejects messages that exceed the maximum allowed size" do + it 'rejects messages that exceed the maximum allowed size' do max_bytes = Defaults::Message::MAX_BYTES - message = {"a" => "b" * max_bytes} + message = { 'a' => 'b' * max_bytes } expect(subject.length).to eq(0) expect { subject << message }.to raise_error(MessageBatch::JSONGenerationError) - .with_message("Message Exceeded Maximum Allowed Size") + .with_message('Message Exceeded Maximum Allowed Size') end end - describe "#full?" do - it "returns true once item count is exceeded" do - 99.times { subject << {a: "b"} } + describe '#full?' do + it 'returns true once item count is exceeded' do + 99.times { subject << { a: 'b' } } expect(subject.full?).to be(false) - subject << {a: "b"} + subject << { a: 'b' } expect(subject.full?).to be(true) end - it "returns true once max size is almost exceeded" do - message = {a: "b" * (Defaults::Message::MAX_BYTES - 10)} + it 'returns true once max size is almost exceeded' do + message = { a: "b" * (Defaults::Message::MAX_BYTES - 10) } message_size = message.to_json.bytesize From ee68d6594f4e402dc4170136b0b3480b32cdb5e2 Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Tue, 28 May 2024 14:12:53 -0500 Subject: [PATCH 319/322] Missed a few single quotes --- lib/segment/analytics/message_batch.rb | 2 +- spec/segment/analytics/message_batch_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/segment/analytics/message_batch.rb b/lib/segment/analytics/message_batch.rb index 19700ac..29e37d2 100644 --- a/lib/segment/analytics/message_batch.rb +++ b/lib/segment/analytics/message_batch.rb @@ -29,7 +29,7 @@ def <<(message) message_json_size = message_json.bytesize if message_too_big?(message_json_size) logger.error('a message exceeded the maximum allowed size') - raise JSONGenerationError, "Message Exceeded Maximum Allowed Size" + raise JSONGenerationError, 'Message Exceeded Maximum Allowed Size' else @messages << message @json_size += message_json_size + 1 # One byte for the comma diff --git a/spec/segment/analytics/message_batch_spec.rb b/spec/segment/analytics/message_batch_spec.rb index 0666dc5..b85930a 100644 --- a/spec/segment/analytics/message_batch_spec.rb +++ b/spec/segment/analytics/message_batch_spec.rb @@ -37,7 +37,7 @@ class Analytics end it 'returns true once max size is almost exceeded' do - message = { a: "b" * (Defaults::Message::MAX_BYTES - 10) } + message = { a: 'b' * (Defaults::Message::MAX_BYTES - 10) } message_size = message.to_json.bytesize From 5153c1acdb610e49ca2ae380225da51799a704cc Mon Sep 17 00:00:00 2001 From: Shane Duvall Date: Wed, 17 Jul 2024 12:56:18 -0500 Subject: [PATCH 320/322] Update Release notes --- History.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/History.md b/History.md index 6417a98..d07c50a 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,16 @@ +2.5.0 / 2024-07-17 +================== + +* Fix silent failures (https://github.com/segmentio/analytics-ruby/pull/269) +* Update to Ruby 3.2 (https://github.com/segmentio/analytics-ruby/pull/262) +* Rename Segment namespace to SegmentIO (https://github.com/segmentio/analytics-ruby/pull/259) +* Lower allocated and retained strings (https://github.com/segmentio/analytics-ruby/pull/258) +* Modify timestamp to have 3 fractional digits (https://github.com/segmentio/analytics-ruby/pull/250 && https://github.com/segmentio/analytics-ruby/pull/251) +* Fix for empty user_id or anonymous_id (https://github.com/segmentio/analytics-ruby/pull/245) +* Not enqueuing test action in real queue (https://github.com/segmentio/analytics-ruby/pull/237) +* Fix test queue reset! documentation (https://github.com/segmentio/analytics-ruby/pull/235) + + 2.4.0 / 2021-05-05 ================== From 5052ed9614baaff1a285e50fad5440f2226a3a53 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 7 Oct 2024 18:21:25 -0400 Subject: [PATCH 321/322] Release 2.5.0. --- .github/workflows/gem-push.yml | 5 +---- RELEASING.md | 25 +++++-------------------- lib/segment/analytics/version.rb | 2 +- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index ec95c5f..6f120bd 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -1,10 +1,7 @@ name: Ruby Gem on: - push: - branches: [ master ] - pull_request: - branches: [ master ] + workflow_dispatch: jobs: build: diff --git a/RELEASING.md b/RELEASING.md index b6041eb..108b55a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,28 +1,13 @@ We automatically push tags to Rubygems via CI. -Pre-releases +Release ============ - Make sure you're on the latest `master` - Bump the version in [`version.rb`](lib/segment/analytics/version.rb) - Update [`History.md`](History.md) -- Commit these changes. `git commit -am "Release x.y.z.pre"` -- Tag the pre-release. `git tag -a -m "Version x.y.z.pre" x.y.z.pre` -- `git push -u origin master && git push --tags`. The tagged commit will be - pushed to RubyGems via Travis - - -Promoting pre-releases -====================== - -- Find the tag for the pre-release you want to promote. Use `git tag --list - '*.pre'` to list all pre-release tags -- Checkout that tag. `git checkout tags/x.y.z.pre` -- Update the version in [`version.rb`](lib/segment/analytics/version.rb) to not - include the `.pre` suffix -- Commit these changes. `git commit -am "Promote x.y.z.pre"` +- Commit these changes. `git commit -am "Release x.y.z."` - Tag the release. `git tag -a -m "Version x.y.z" x.y.z` -- `git push -u origin master && git push --tags`. The tagged commit will be - pushed to RubyGems via Travis -- On `master`, add an entry to [`History.md`](History.md) under `x.y.z` that - says 'Promoted pre-release to stable' +- `git push -u origin master && git push --tags +- Run the publish action on Github + diff --git a/lib/segment/analytics/version.rb b/lib/segment/analytics/version.rb index eaabbdf..8416349 100644 --- a/lib/segment/analytics/version.rb +++ b/lib/segment/analytics/version.rb @@ -2,6 +2,6 @@ module Segment class Analytics - VERSION = '2.4.2' + VERSION = '2.5.0' end end From 132aa1967ded1c5204df14945c9d360a2b914fb5 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 7 Oct 2024 18:54:24 -0400 Subject: [PATCH 322/322] Removing github gem publish step --- .github/workflows/gem-push.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/gem-push.yml b/.github/workflows/gem-push.yml index 6f120bd..5deca81 100644 --- a/.github/workflows/gem-push.yml +++ b/.github/workflows/gem-push.yml @@ -18,18 +18,6 @@ jobs: with: ruby-version: 2.7.7 - - name: Publish to GPR - run: | - mkdir -p $HOME/.gem - touch $HOME/.gem/credentials - chmod 0600 $HOME/.gem/credentials - printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials - gem build *.gemspec - gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem - env: - GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}" - OWNER: ${{ github.repository_owner }} - - name: Publish to RubyGems run: | mkdir -p $HOME/.gem