diff --git a/lib/salesmachine/api/client.rb b/lib/salesmachine/api/client.rb index 06d9c26..d324c88 100644 --- a/lib/salesmachine/api/client.rb +++ b/lib/salesmachine/api/client.rb @@ -12,20 +12,20 @@ class Client # public: Creates a new client # # attrs - Hash - # :api_key - String of your project's api_key + # :api_token - String of your project's api_token # :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 @queue = Queue.new - @api_key = attrs[:api_key] + @api_token = attrs[:api_token] @max_queue_size = attrs[:max_queue_size] || Config::Queue::MAX_SIZE @options = attrs @worker_mutex = Mutex.new - @worker = Worker.new @queue, @api_key, @options + @worker = Worker.new @queue, @api_token, @options - check_api_key! + check_api_token! at_exit { @worker_thread && @worker_thread[:should_exit] = true } end @@ -46,23 +46,23 @@ def flush # attrs - Hash def track attrs symbolize_keys! attrs - check_contact_id! attrs + check_contact_uid! attrs - event = attrs[:event] + event_uid = attrs[:event_uid] params = attrs[:params] || {} created_at = attrs[:created_at] || Time.new check_timestamp! created_at - if event.nil? || event.empty? - fail ArgumentError, 'Must supply event as a non-empty string' + if event_uid.nil? || event_uid.empty? + fail ArgumentError, 'Must supply event_uid as a non-empty string' end fail ArgumentError, 'Params must be a Hash' unless params.is_a? Hash isoify_dates! params enqueue({ - :event => event, + :event_uid => event_uid, :contact_uid => attrs[:contact_uid], :params => params, :created_at => datetime_in_iso8601(created_at), @@ -75,7 +75,7 @@ def track attrs # attrs - Hash def email attrs symbolize_keys! attrs - check_contact_id! attrs + check_contact_uid! attrs email = attrs[:email] params = attrs[:params] || {} @@ -83,27 +83,31 @@ def email attrs check_timestamp! created_at - if email.nil? || evemailent.empty? + if email.nil? || email.empty? fail ArgumentError, 'Must supply email template as a non-empty string' end fail ArgumentError, 'Params must be a Hash' unless params.is_a? Hash isoify_dates! params - enqueue({ - :event => event, + msg = enqueue({ + :email => email, :contact_uid => attrs[:contact_uid], :params => params, :created_at => datetime_in_iso8601(created_at), :method => 'email' }) + + flush if msg + msg end def contact attrs symbolize_keys! attrs - check_contact_id! attrs + check_contact_uid! attrs + contact_uid = attrs[:contact_uid] params = attrs[:params] || {} created_at = attrs[:created_at] || Time.new @@ -113,7 +117,7 @@ def contact attrs isoify_dates! params enqueue({ - :contact_uid => attrs[:contact_uid], + :contact_uid => contact_uid, :params => params, :created_at => datetime_in_iso8601(created_at), :method => 'contact' @@ -123,7 +127,7 @@ def contact attrs def account(attrs) symbolize_keys! attrs - fail ArgumentError, 'Must supply a contact_uid' unless attrs[:account_uid] + fail ArgumentError, 'Must supply a account_uid' unless attrs[:account_uid] account_uid = attrs[:account_uid] params = attrs[:params] || {} @@ -144,7 +148,7 @@ def account(attrs) def pageview(attrs) symbolize_keys! attrs - check_contact_id! attrs + check_contact_uid! attrs params = attrs[:params] || {} created_at = attrs[:created_at] || Time.new @@ -156,80 +160,77 @@ def pageview(attrs) enqueue({ :contact_uid => attrs[:contact_uid], - :event => "pageview", + :event_uid => "pageview", :params => attrs[:params], :created_at => datetime_in_iso8601(created_at), - :method => 'pageview' + :method => 'event' }) end - # public: Returns the number of queued messages - # - # returns Fixnum of messages in the queue - def queued_messages + def queued_nb @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[:messageId] = uid - unless queue_full = @queue.length >= @max_queue_size - ensure_worker_running - @queue << action + # 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[:messageId] = create_uid() + unless queue_full = @queue.length >= @max_queue_size + ensure_worker_running + @queue << action + end + queue_full ? !queue_full : action end - !queue_full - 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?) - fail ArgumentError, "#{name} must be given" + # 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?) + fail ArgumentError, "#{name} must be given" + end end - end - # private: Adds contextual information to the call - # - # context - Hash of call context - def add_context(context) - context[:library] = { :name => "salesmachine-ruby", :version => Salesmachine::Api::VERSION.to_s } - end + # private: Adds contextual information to the call + # + # context - Hash of call context + def add_context(context) + context[:library] = { :name => "salesmachine-ruby", :version => Salesmachine::Api::VERSION.to_s } + end - # private: Checks that the api_key is properly initialized - def check_api_key! - fail ArgumentError, 'Api key must be initialized' if @api_key.nil? - end + # private: Checks that the api_token is properly initialized + def check_api_token! + fail ArgumentError, 'Api key must be initialized' if @api_token.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 + # 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 - def check_contact_id! attrs - fail ArgumentError, 'Must supply a contact_uid' unless attrs[:contact_uid] - end + def check_contact_uid! attrs + fail ArgumentError, 'Must supply a contact_uid' unless attrs[:contact_uid] + end - def ensure_worker_running - return if worker_running? - @worker_mutex.synchronize do + def ensure_worker_running return if worker_running? - @worker_thread = Thread.new do - @worker.run + @worker_mutex.synchronize do + return if worker_running? + @worker_thread = Thread.new do + @worker.run + end end end - end - def worker_running? - @worker_thread && @worker_thread.alive? - end + def worker_running? + @worker_thread && @worker_thread.alive? + end end end end diff --git a/lib/salesmachine/api/config.rb b/lib/salesmachine/api/config.rb index 82d4e65..e1b4d6b 100644 --- a/lib/salesmachine/api/config.rb +++ b/lib/salesmachine/api/config.rb @@ -4,7 +4,7 @@ module Config module Request HOST = 'play.salesmachine.net' PORT = 443 - PATH = '/v1/bulk' + PATH = '/v1/batch' SSL = true HEADERS = { :accept => 'application/json' } RETRIES = 4 diff --git a/lib/salesmachine/api/request.rb b/lib/salesmachine/api/request.rb index 63d4a00..39db01a 100644 --- a/lib/salesmachine/api/request.rb +++ b/lib/salesmachine/api/request.rb @@ -36,33 +36,32 @@ def initialize(options = {}) # public: Posts the write key and batch of messages to the API. # # returns - Response of the status and error if it exists - def post(api_key, batch) + def post(api_token, batch) status, error = nil, nil remaining_retries = @retries backoff = @backoff headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' } begin -# payload = JSON.generate :api_token=>api_key, :encode=>"base64", :data=>batch payload = batch.to_json request = Net::HTTP::Post.new(@path, headers) - request.basic_auth api_key, api_key + request.basic_auth api_token, api_token if self.class.stub status = 200 error = nil - logger.debug "stubbed request to #{@path}: write key = #{api_key}, payload = #{payload}" + logger.debug "stubbed request to #{@path}: write key = #{api_token}, payload = #{payload}" else res = @http.request(request, payload) status = res.code.to_i - unless status==200 or status==201 + unless status == 200 or status == 201 body = JSON.parse(res.body) - error = body["error"] + error = body["error"] end end rescue Exception => e - unless (remaining_retries -=1).zero? + unless (remaining_retries -= 1).zero? sleep(backoff) retry end diff --git a/lib/salesmachine/api/utils.rb b/lib/salesmachine/api/utils.rb index f88dd28..8b4fb33 100644 --- a/lib/salesmachine/api/utils.rb +++ b/lib/salesmachine/api/utils.rb @@ -41,7 +41,7 @@ def isoify_dates!(hash) # public: Returns a uid string # - def uid + def create_uid arr = SecureRandom.random_bytes(16).unpack("NnnnnN") arr[2] = (arr[2] & 0x0fff) | 0x4000 arr[3] = (arr[3] & 0x3fff) | 0x8000 diff --git a/lib/salesmachine/api/worker.rb b/lib/salesmachine/api/worker.rb index 2c992e0..629811f 100644 --- a/lib/salesmachine/api/worker.rb +++ b/lib/salesmachine/api/worker.rb @@ -14,15 +14,15 @@ class Worker # and makes requests to the salesmachine.io api # # queue - Queue synchronized between client and worker - # api_key - String of the application's Api key + # api_token - String of the application's Api key # 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 # - def initialize(queue, api_key, options = {}) + def initialize(queue, api_token, options = {}) symbolize_keys! options @queue = queue - @api_key = api_key + @api_token = api_token @batch_size = options[:batch_size] || Queue::BATCH_SIZE @on_error = options[:on_error] || Proc.new { |status, error| } @batch = [] @@ -41,7 +41,7 @@ def run end end - res = Request.new.post @api_key, @batch + res = Request.new.post @api_token, @batch @lock.synchronize { @batch.clear } diff --git a/salesmachine-ruby.gemspec b/salesmachine-ruby.gemspec index 5ac0db8..ec26cb8 100644 --- a/salesmachine-ruby.gemspec +++ b/salesmachine-ruby.gemspec @@ -6,8 +6,8 @@ require 'salesmachine/api/version' Gem::Specification.new do |spec| spec.name = "salesmachine-ruby" spec.version = Salesmachine::Api::VERSION - spec.authors = ["citizen75"] - spec.email = ["gsamoun@gmail.com"] + spec.authors = ["salesmachine-team"] + spec.email = ["support@salesmachine.io"] spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.} spec.description = %q{TODO: Write a longer description or delete this line.} diff --git a/spec/salesmachine/account_spec.rb b/spec/salesmachine/account_spec.rb index 38d7d3e..4d78f5c 100644 --- a/spec/salesmachine/account_spec.rb +++ b/spec/salesmachine/account_spec.rb @@ -6,12 +6,11 @@ class Api describe '#account' do before :all do - @client = Client.new :api_key => API_KEY + @client = Client.new :api_token => API_TOKEN @queue = @client.instance_variable_get :@queue end after :each do -# @queue.clear @client.flush end diff --git a/spec/salesmachine/api_spec.rb b/spec/salesmachine/api_spec.rb index 007fa8a..5cfed93 100644 --- a/spec/salesmachine/api_spec.rb +++ b/spec/salesmachine/api_spec.rb @@ -5,16 +5,16 @@ class Api describe Client do describe '#initialize' do - it 'should error if no api_key is supplied' do + it 'should error if no api_token is supplied' do expect { Client.new }.to raise_error(ArgumentError) end - it 'should not error if a api_key is supplied' do - Client.new :api_key => API_KEY + it 'should not error if a api_token is supplied' do + Client.new :api_token => API_TOKEN end - it 'should not error if a api_key is supplied as a string' do - Client.new 'api_key' => API_KEY + it 'should not error if a api_token is supplied as a string' do + Client.new 'api_token' => API_TOKEN end end end diff --git a/spec/salesmachine/contact_spec.rb b/spec/salesmachine/contact_spec.rb index 310173d..c121d9d 100644 --- a/spec/salesmachine/contact_spec.rb +++ b/spec/salesmachine/contact_spec.rb @@ -6,36 +6,38 @@ class Api describe '#contact' do before(:all) do - @client = Client.new :api_key => API_KEY + @client = Client.new :api_token => API_TOKEN @queue = @client.instance_variable_get :@queue end + after :each do + @client.flush + end + it 'should error without any contact id' do expect { @client.contact({}) }.to raise_error(ArgumentError) end it 'should not error with the required options' do @client.contact Queued::CONTACT -# @queue.pop end it 'should not error with the required options as strings' do @client.contact Utils.stringify_keys(Queued::CONTACT) - @queue.pop end it 'should convert time and date params into iso8601 format' do @client.contact({ - :contact_uid => 'user', + :contact_uid => CONTACT_UID, :params => { :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), - :nottime => 'x' + :nottime => 'x', + :account_uid => ACCOUNT_UID } }) - message = @queue.pop message[:params][:time].should == '2013-01-01T00:00:00.000Z' message[:params][:time_with_zone].should == '2013-01-01T00:00:00.000Z' message[:params][:date_time].should == '2013-01-01T00:00:00.000Z' diff --git a/spec/salesmachine/email_spec.rb b/spec/salesmachine/email_spec.rb new file mode 100644 index 0000000..6218ec0 --- /dev/null +++ b/spec/salesmachine/email_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +module Salesmachine + class Api + describe Client do + + describe '#email' do + before(:all) do + @client = Client.new :api_token => API_TOKEN + @queue = @client.instance_variable_get :@queue + end + + after :each do + @client.flush + end + + it 'should error without an email' do + expect { @client.email(:contact_uid => 'user') }.to raise_error(ArgumentError) + end + + it 'should error without a contact_uid' do + expect { @client.email(:email => 'email_uid') }.to raise_error(ArgumentError) + end + + it 'should error if params is not a hash' do + expect { + @client.email({ + :contact_uid => CONTACT_UID, + :email => 'email', + :params => [1,2,3] + }) + }.to raise_error(ArgumentError) + end + + it 'should not error with the required options' do + @client.email Queued::EMAIL + end + + it 'should not error when given string keys' do + @client.email Utils.stringify_keys(Queued::EMAIL) + end + + it 'should use the timestamp given' do + time = Time.parse("1990-07-16 13:30:00.123 UTC") + + msg = @client.email({ + :contact_uid => CONTACT_UID, + :email => 'email', + :created_at => time + }) + + Time.parse(msg[:created_at]).should == time + end + + it 'should flush the queue after an email has been added' do + @client.track({ + :contact_uid => CONTACT_UID, + :event_uid => 'testing the timestamp' + }) + + @client.email({ + :contact_uid => CONTACT_UID, + :email => 'email' + }) + + @client.queued_nb.should == 0 + end + + end + end + end +end \ No newline at end of file diff --git a/spec/salesmachine/event_spec.rb b/spec/salesmachine/event_spec.rb index 1296b5c..045f602 100644 --- a/spec/salesmachine/event_spec.rb +++ b/spec/salesmachine/event_spec.rb @@ -6,12 +6,11 @@ class Api describe '#track' do before(:all) do - @client = Client.new :api_key => API_KEY + @client = Client.new :api_token => API_TOKEN @queue = @client.instance_variable_get :@queue end after :each do -# @queue.clear @client.flush end @@ -20,14 +19,14 @@ class Api end it 'should error without a contact_uid' do - expect { @client.track(:event => 'Event') }.to raise_error(ArgumentError) + expect { @client.track(:event_uid => 'event') }.to raise_error(ArgumentError) end it 'should error if params is not a hash' do expect { @client.track({ - :contact_uid => 'user', - :event => 'Event', + :contact_uid => CONTACT_UID, + :event_uid => 'event', :params => [1,2,3] }) }.to raise_error(ArgumentError) @@ -37,39 +36,38 @@ class Api time = Time.parse("1990-07-16 13:30:00.123 UTC") @client.track({ - :event => 'testing the timestamp', - :contact_uid => 'joe', - :created_at => time + :contact_uid => CONTACT_UID, + :event_uid => 'testing the timestamp', + :created_at => time, + :params => { + :account_uid => ACCOUNT_UID + } }) - msg = @queue.pop - Time.parse(msg[:created_at]).should == time 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 and date traits into iso8601 format' do @client.track({ - :contact_uid => 'user', - :event => 'Event', + :contact_uid => CONTACT_UID, + :event_uid => 'event', :params => { :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), - :nottime => 'x' + :nottime => 'x', + :account_uid => ACCOUNT_UID } }) - message = @queue.pop message[:params][:time].should == '2013-01-01T00:00:00.000Z' message[:params][:time_with_zone].should == '2013-01-01T00:00:00.000Z' message[:params][:date_time].should == '2013-01-01T00:00:00.000Z' diff --git a/spec/salesmachine/pageview_spec.rb b/spec/salesmachine/pageview_spec.rb index a32c9db..7262710 100644 --- a/spec/salesmachine/pageview_spec.rb +++ b/spec/salesmachine/pageview_spec.rb @@ -6,7 +6,11 @@ class Api describe '#pageview' do before :all do - @client = Client.new :api_key => API_KEY + @client = Client.new :api_token => API_TOKEN + end + + after :each do + @client.flush end it 'should error without contact_uid' do @@ -19,7 +23,6 @@ class Api it 'should not error with the required options as strings' do @client.pageview Utils.stringify_keys(Queued::PAGEVIEW) - @client.flush end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e64822b..b530ca4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,10 +9,10 @@ module Salesmachine class Api - API_KEY = '' + API_TOKEN = '' API_SECRET = '' - CONTACT = { + CONTACT = { :params => { :name => "John Doe", :email => "jdoe@acme.com", @@ -21,22 +21,21 @@ class Api } TRACK = { - :event => 'signed_up', + :event_uid => 'signed_up', :params => { :referrer => 'Google', - :created => Time.new + :created => Time.new, } } PAGEVIEW = { - :event => 'pageview', + :event_uid => 'pageview', :params => { :visit_ip => '46.228.47.114', :visit_url=> 'http://www.yahoo.com' } } - ACCOUNT = { :params => { :name => "Acme Inc.", @@ -44,15 +43,31 @@ class Api } } - CONTACT_ID = 1234 - ACCOUNT_ID = 1234 + EMAIL = { + :email => "My Email title", + :params => { + :type => "Newsletter email" + } + } + + CONTACT_UID = 1234 + ACCOUNT_UID = 1234 # Hashes sent to the client, snake_case module Queued - TRACK = TRACK.merge :contact_uid => CONTACT_ID - PAGEVIEW = PAGEVIEW.merge :contact_uid => CONTACT_ID - CONTACT = CONTACT.merge :contact_uid => CONTACT_ID - ACCOUNT = ACCOUNT.merge :account_uid => ACCOUNT_ID + TRACK = TRACK.merge :contact_uid => CONTACT_UID + TRACK[:params] = TRACK[:params].merge :account_uid => ACCOUNT_UID + + PAGEVIEW = PAGEVIEW.merge :contact_uid => CONTACT_UID + PAGEVIEW[:params] = PAGEVIEW[:params].merge :account_uid => ACCOUNT_UID + + EMAIL = EMAIL.merge :contact_uid => CONTACT_UID + EMAIL[:params] = EMAIL[:params].merge :account_uid => ACCOUNT_UID + + CONTACT = CONTACT.merge :contact_uid => CONTACT_UID + CONTACT[:params] = CONTACT[:params].merge :account_uid => ACCOUNT_UID + + ACCOUNT = ACCOUNT.merge :account_uid => ACCOUNT_UID end