plainflow 2.2.3.pre.p1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/History.md +200 -0
- data/Makefile +11 -0
- data/README.md +66 -0
- data/RELEASING.md +10 -0
- data/Rakefile +7 -0
- data/bin/plainflow +152 -0
- data/lib/plainflow/analytics.rb +31 -0
- data/lib/plainflow/analytics/client.rb +376 -0
- data/lib/plainflow/analytics/defaults.rb +20 -0
- data/lib/plainflow/analytics/logging.rb +35 -0
- data/lib/plainflow/analytics/request.rb +82 -0
- data/lib/plainflow/analytics/response.rb +16 -0
- data/lib/plainflow/analytics/utils.rb +88 -0
- data/lib/plainflow/analytics/version.rb +5 -0
- data/lib/plainflow/analytics/worker.rb +60 -0
- data/lib/segment.rb +1 -0
- data/plainflow-ruby.gemspec +25 -0
- data/spec/plainflow/analytics/client_spec.rb +311 -0
- data/spec/plainflow/analytics/request_spec.rb +191 -0
- data/spec/plainflow/analytics/response_spec.rb +30 -0
- data/spec/plainflow/analytics/worker_spec.rb +102 -0
- data/spec/plainflow/analytics_spec.rb +120 -0
- data/spec/spec_helper.rb +102 -0
- metadata +144 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'plainflow/analytics/defaults'
|
|
2
|
+
require 'plainflow/analytics/utils'
|
|
3
|
+
require 'plainflow/analytics/response'
|
|
4
|
+
require 'plainflow/analytics/logging'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'net/https'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
module Plainflow
|
|
10
|
+
class Analytics
|
|
11
|
+
class Request
|
|
12
|
+
include Plainflow::Analytics::Defaults::Request
|
|
13
|
+
include Plainflow::Analytics::Utils
|
|
14
|
+
include Plainflow::Analytics::Logging
|
|
15
|
+
|
|
16
|
+
# public: Creates a new request object to send analytics batch
|
|
17
|
+
#
|
|
18
|
+
def initialize(options = {})
|
|
19
|
+
options[:host] ||= HOST
|
|
20
|
+
options[:port] ||= PORT
|
|
21
|
+
options[:ssl] ||= SSL
|
|
22
|
+
options[:headers] ||= HEADERS
|
|
23
|
+
@path = options[:path] || PATH
|
|
24
|
+
@retries = options[:retries] || RETRIES
|
|
25
|
+
@backoff = options[:backoff] || BACKOFF
|
|
26
|
+
|
|
27
|
+
http = Net::HTTP.new(options[:host], options[:port])
|
|
28
|
+
http.use_ssl = options[:ssl]
|
|
29
|
+
http.read_timeout = 8
|
|
30
|
+
http.open_timeout = 4
|
|
31
|
+
|
|
32
|
+
@http = http
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# public: Posts the write key and batch of messages to the API.
|
|
36
|
+
#
|
|
37
|
+
# returns - Response of the status and error if it exists
|
|
38
|
+
def post(secret_key, batch)
|
|
39
|
+
status, error = nil, nil
|
|
40
|
+
remaining_retries = @retries
|
|
41
|
+
backoff = @backoff
|
|
42
|
+
headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' }
|
|
43
|
+
begin
|
|
44
|
+
payload = JSON.generate :sentAt => datetime_in_iso8601(Time.new), :batch => batch
|
|
45
|
+
request = Net::HTTP::Post.new(@path, headers)
|
|
46
|
+
request.basic_auth secret_key, nil
|
|
47
|
+
|
|
48
|
+
if self.class.stub
|
|
49
|
+
status = 200
|
|
50
|
+
error = nil
|
|
51
|
+
logger.debug "stubbed request to #{@path}: write key = #{secret_key}, payload = #{payload}"
|
|
52
|
+
else
|
|
53
|
+
res = @http.request(request, payload)
|
|
54
|
+
status = res.code.to_i
|
|
55
|
+
body = JSON.parse(res.body)
|
|
56
|
+
error = body["error"]
|
|
57
|
+
end
|
|
58
|
+
rescue Exception => e
|
|
59
|
+
unless (remaining_retries -=1).zero?
|
|
60
|
+
sleep(backoff)
|
|
61
|
+
retry
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
logger.error e.message
|
|
65
|
+
e.backtrace.each { |line| logger.error line }
|
|
66
|
+
status = -1
|
|
67
|
+
error = "Connection error: #{e}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Response.new status, error
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
attr_accessor :stub
|
|
75
|
+
|
|
76
|
+
def stub
|
|
77
|
+
@stub || ENV['STUB']
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Plainflow
|
|
2
|
+
class Analytics
|
|
3
|
+
class Response
|
|
4
|
+
attr_reader :status, :error
|
|
5
|
+
|
|
6
|
+
# public: Simple class to wrap responses from the API
|
|
7
|
+
#
|
|
8
|
+
#
|
|
9
|
+
def initialize(status = 200, error = nil)
|
|
10
|
+
@status = status
|
|
11
|
+
@error = error
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module Plainflow
|
|
4
|
+
class Analytics
|
|
5
|
+
module Utils
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# public: Return a new hash with keys converted from strings to symbols
|
|
9
|
+
#
|
|
10
|
+
def symbolize_keys(hash)
|
|
11
|
+
hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# public: Convert hash keys from strings to symbols in place
|
|
15
|
+
#
|
|
16
|
+
def symbolize_keys!(hash)
|
|
17
|
+
hash.replace symbolize_keys hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# public: Return a new hash with keys as strings
|
|
21
|
+
#
|
|
22
|
+
def stringify_keys(hash)
|
|
23
|
+
hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# public: Returns a new hash with all the date values in the into iso8601
|
|
27
|
+
# strings
|
|
28
|
+
#
|
|
29
|
+
def isoify_dates(hash)
|
|
30
|
+
hash.inject({}) { |memo, (k, v)|
|
|
31
|
+
memo[k] = datetime_in_iso8601(v)
|
|
32
|
+
memo
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# public: Converts all the date values in the into iso8601 strings in place
|
|
37
|
+
#
|
|
38
|
+
def isoify_dates!(hash)
|
|
39
|
+
hash.replace isoify_dates hash
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# public: Returns a uid string
|
|
43
|
+
#
|
|
44
|
+
def uid
|
|
45
|
+
arr = SecureRandom.random_bytes(16).unpack("NnnnnN")
|
|
46
|
+
arr[2] = (arr[2] & 0x0fff) | 0x4000
|
|
47
|
+
arr[3] = (arr[3] & 0x3fff) | 0x8000
|
|
48
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % arr
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def datetime_in_iso8601 datetime
|
|
52
|
+
case datetime
|
|
53
|
+
when Time
|
|
54
|
+
time_in_iso8601 datetime
|
|
55
|
+
when DateTime
|
|
56
|
+
time_in_iso8601 datetime.to_time
|
|
57
|
+
when Date
|
|
58
|
+
date_in_iso8601 datetime
|
|
59
|
+
else
|
|
60
|
+
datetime
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def time_in_iso8601 time, fraction_digits = 3
|
|
65
|
+
fraction = if fraction_digits > 0
|
|
66
|
+
(".%06i" % time.usec)[0, fraction_digits + 1]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(time, true, 'Z')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def date_in_iso8601 date
|
|
73
|
+
date.strftime("%F")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def formatted_offset time, colon = true, alternate_utc_string = nil
|
|
77
|
+
time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def seconds_to_utc_offset(seconds, colon = true)
|
|
81
|
+
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
|
|
85
|
+
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'plainflow/analytics/defaults'
|
|
2
|
+
require 'plainflow/analytics/utils'
|
|
3
|
+
require 'plainflow/analytics/defaults'
|
|
4
|
+
require 'plainflow/analytics/request'
|
|
5
|
+
|
|
6
|
+
module Plainflow
|
|
7
|
+
class Analytics
|
|
8
|
+
class Worker
|
|
9
|
+
include Plainflow::Analytics::Utils
|
|
10
|
+
include Plainflow::Analytics::Defaults
|
|
11
|
+
|
|
12
|
+
# public: Creates a new worker
|
|
13
|
+
#
|
|
14
|
+
# The worker continuously takes messages off the queue
|
|
15
|
+
# and makes requests to the plainflow.com api
|
|
16
|
+
#
|
|
17
|
+
# queue - Queue synchronized between client and worker
|
|
18
|
+
# secret_key - String of the project's Write key
|
|
19
|
+
# options - Hash of worker options
|
|
20
|
+
# batch_size - Fixnum of how many items to send in a batch
|
|
21
|
+
# on_error - Proc of what to do on an error
|
|
22
|
+
#
|
|
23
|
+
def initialize(queue, secret_key, options = {})
|
|
24
|
+
symbolize_keys! options
|
|
25
|
+
@queue = queue
|
|
26
|
+
@secret_key = secret_key
|
|
27
|
+
@batch_size = options[:batch_size] || Queue::BATCH_SIZE
|
|
28
|
+
@on_error = options[:on_error] || Proc.new { |status, error| }
|
|
29
|
+
@batch = []
|
|
30
|
+
@lock = Mutex.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# public: Continuously runs the loop to check for new events
|
|
34
|
+
#
|
|
35
|
+
def run
|
|
36
|
+
until Thread.current[:should_exit]
|
|
37
|
+
return if @queue.empty?
|
|
38
|
+
|
|
39
|
+
@lock.synchronize do
|
|
40
|
+
until @batch.length >= @batch_size || @queue.empty?
|
|
41
|
+
@batch << @queue.pop
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
res = Request.new.post @secret_key, @batch
|
|
46
|
+
|
|
47
|
+
@on_error.call res.status, res.error unless res.status == 200
|
|
48
|
+
|
|
49
|
+
@lock.synchronize { @batch.clear }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# public: Check whether we have outstanding requests.
|
|
54
|
+
#
|
|
55
|
+
def is_requesting?
|
|
56
|
+
@lock.synchronize { !@batch.empty? }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/segment.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'plainflow/analytics'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require File.expand_path('../lib/plainflow/analytics/version', __FILE__)
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'plainflow'
|
|
5
|
+
spec.version = Plainflow::Analytics::VERSION
|
|
6
|
+
spec.files = Dir.glob('**/*')
|
|
7
|
+
spec.require_paths = ['lib']
|
|
8
|
+
spec.bindir = 'bin'
|
|
9
|
+
spec.executables = ['plainflow']
|
|
10
|
+
spec.summary = 'Plainflow library'
|
|
11
|
+
spec.description = 'The Plainflow ruby library'
|
|
12
|
+
spec.authors = ['Plainflow']
|
|
13
|
+
spec.email = 'friends@plainflow.com'
|
|
14
|
+
spec.homepage = 'https://github.com/plainflow-dcp/plainflow-ruby'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
# Ruby 1.8 requires json
|
|
18
|
+
spec.add_dependency 'json', ['~> 1.7'] if RUBY_VERSION < "1.9"
|
|
19
|
+
spec.add_dependency 'commander', '~> 4.4'
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency 'rake', '~> 10.3'
|
|
22
|
+
spec.add_development_dependency 'rspec', '~> 2.0'
|
|
23
|
+
spec.add_development_dependency 'tzinfo', '1.2.1'
|
|
24
|
+
spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0'
|
|
25
|
+
end
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Plainflow
|
|
4
|
+
class Analytics
|
|
5
|
+
describe Client do
|
|
6
|
+
let(:client) { Client.new :secret_key => SECRET_KEY }
|
|
7
|
+
let(:queue) { client.instance_variable_get :@queue }
|
|
8
|
+
|
|
9
|
+
describe '#initialize' do
|
|
10
|
+
it 'errors if no secret_key is supplied' do
|
|
11
|
+
expect { Client.new }.to raise_error(ArgumentError)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'does not error if a secret_key is supplied' do
|
|
15
|
+
expect do
|
|
16
|
+
Client.new :secret_key => SECRET_KEY
|
|
17
|
+
end.to_not raise_error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'does not error if a secret_key is supplied as a string' do
|
|
21
|
+
expect do
|
|
22
|
+
Client.new 'secret_key' => SECRET_KEY
|
|
23
|
+
end.to_not raise_error
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#track' do
|
|
28
|
+
it 'errors without an event' do
|
|
29
|
+
expect { client.track(:user_id => 'user') }.to raise_error(ArgumentError)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'errors without a user_id' do
|
|
33
|
+
expect { client.track(:event => 'Event') }.to raise_error(ArgumentError)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'errors if properties is not a hash' do
|
|
37
|
+
expect {
|
|
38
|
+
client.track({
|
|
39
|
+
:user_id => 'user',
|
|
40
|
+
:event => 'Event',
|
|
41
|
+
:properties => [1,2,3]
|
|
42
|
+
})
|
|
43
|
+
}.to raise_error(ArgumentError)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'uses the timestamp given' do
|
|
47
|
+
time = Time.parse("1990-07-16 13:30:00.123 UTC")
|
|
48
|
+
|
|
49
|
+
client.track({
|
|
50
|
+
:event => 'testing the timestamp',
|
|
51
|
+
:user_id => 'joe',
|
|
52
|
+
:timestamp => time
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
msg = queue.pop
|
|
56
|
+
|
|
57
|
+
expect(Time.parse(msg[:timestamp])).to eq(time)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'does not error with the required options' do
|
|
61
|
+
expect do
|
|
62
|
+
client.track Queued::TRACK
|
|
63
|
+
queue.pop
|
|
64
|
+
end.to_not raise_error
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'does not error when given string keys' do
|
|
68
|
+
expect do
|
|
69
|
+
client.track Utils.stringify_keys(Queued::TRACK)
|
|
70
|
+
queue.pop
|
|
71
|
+
end.to_not raise_error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'converts time and date traits into iso8601 format' do
|
|
75
|
+
client.track({
|
|
76
|
+
:user_id => 'user',
|
|
77
|
+
:event => 'Event',
|
|
78
|
+
:properties => {
|
|
79
|
+
:time => Time.utc(2013),
|
|
80
|
+
:time_with_zone => Time.zone.parse('2013-01-01'),
|
|
81
|
+
:date_time => DateTime.new(2013,1,1),
|
|
82
|
+
:date => Date.new(2013,1,1),
|
|
83
|
+
:nottime => 'x'
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
message = queue.pop
|
|
87
|
+
|
|
88
|
+
expect(message[:properties][:time]).to eq('2013-01-01T00:00:00.000Z')
|
|
89
|
+
expect(message[:properties][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
|
|
90
|
+
expect(message[:properties][:date_time]).to eq('2013-01-01T00:00:00.000Z')
|
|
91
|
+
expect(message[:properties][:date]).to eq('2013-01-01')
|
|
92
|
+
expect(message[:properties][:nottime]).to eq('x')
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
describe '#identify' do
|
|
98
|
+
it 'errors without any user id' do
|
|
99
|
+
expect { client.identify({}) }.to raise_error(ArgumentError)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'does not error with the required options' do
|
|
103
|
+
expect do
|
|
104
|
+
client.identify Queued::IDENTIFY
|
|
105
|
+
queue.pop
|
|
106
|
+
end.to_not raise_error
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'does not error with the required options as strings' do
|
|
110
|
+
expect do
|
|
111
|
+
client.identify Utils.stringify_keys(Queued::IDENTIFY)
|
|
112
|
+
queue.pop
|
|
113
|
+
end.to_not raise_error
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'converts time and date traits into iso8601 format' do
|
|
117
|
+
client.identify({
|
|
118
|
+
:user_id => 'user',
|
|
119
|
+
:traits => {
|
|
120
|
+
:time => Time.utc(2013),
|
|
121
|
+
:time_with_zone => Time.zone.parse('2013-01-01'),
|
|
122
|
+
:date_time => DateTime.new(2013,1,1),
|
|
123
|
+
:date => Date.new(2013,1,1),
|
|
124
|
+
:nottime => 'x'
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
message = queue.pop
|
|
129
|
+
|
|
130
|
+
expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z')
|
|
131
|
+
expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
|
|
132
|
+
expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z')
|
|
133
|
+
expect(message[:traits][:date]).to eq('2013-01-01')
|
|
134
|
+
expect(message[:traits][:nottime]).to eq('x')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe '#alias' do
|
|
139
|
+
it 'errors without from' do
|
|
140
|
+
expect { client.alias :user_id => 1234 }.to raise_error(ArgumentError)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'errors without to' do
|
|
144
|
+
expect { client.alias :previous_id => 1234 }.to raise_error(ArgumentError)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'does not error with the required options' do
|
|
148
|
+
expect { client.alias ALIAS }.to_not raise_error
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'does not error with the required options as strings' do
|
|
152
|
+
expect do
|
|
153
|
+
client.alias Utils.stringify_keys(ALIAS)
|
|
154
|
+
end.to_not raise_error
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
describe '#group' do
|
|
159
|
+
after do
|
|
160
|
+
client.flush
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'errors without group_id' do
|
|
164
|
+
expect { client.group :user_id => 'foo' }.to raise_error(ArgumentError)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'errors without user_id' do
|
|
168
|
+
expect { client.group :group_id => 'foo' }.to raise_error(ArgumentError)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'does not error with the required options' do
|
|
172
|
+
client.group Queued::GROUP
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'does not error with the required options as strings' do
|
|
176
|
+
client.group Utils.stringify_keys(Queued::GROUP)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'converts time and date traits into iso8601 format' do
|
|
180
|
+
client.identify({
|
|
181
|
+
:user_id => 'user',
|
|
182
|
+
:group_id => 'group',
|
|
183
|
+
:traits => {
|
|
184
|
+
:time => Time.utc(2013),
|
|
185
|
+
:time_with_zone => Time.zone.parse('2013-01-01'),
|
|
186
|
+
:date_time => DateTime.new(2013,1,1),
|
|
187
|
+
:date => Date.new(2013,1,1),
|
|
188
|
+
:nottime => 'x'
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
message = queue.pop
|
|
193
|
+
|
|
194
|
+
expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z')
|
|
195
|
+
expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
|
|
196
|
+
expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z')
|
|
197
|
+
expect(message[:traits][:date]).to eq('2013-01-01')
|
|
198
|
+
expect(message[:traits][:nottime]).to eq('x')
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
describe '#page' do
|
|
203
|
+
it 'errors without user_id' do
|
|
204
|
+
expect { client.page :name => 'foo' }.to raise_error(ArgumentError)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'does not error with the required options' do
|
|
208
|
+
expect { client.page Queued::PAGE }.to_not raise_error
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'does not error with the required options as strings' do
|
|
212
|
+
expect do
|
|
213
|
+
client.page Utils.stringify_keys(Queued::PAGE)
|
|
214
|
+
end.to_not raise_error
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe '#screen' do
|
|
219
|
+
it 'errors without user_id' do
|
|
220
|
+
expect { client.screen :name => 'foo' }.to raise_error(ArgumentError)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'does not error with the required options' do
|
|
224
|
+
expect { client.screen Queued::SCREEN }.to_not raise_error
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'does not error with the required options as strings' do
|
|
228
|
+
expect do
|
|
229
|
+
client.screen Utils.stringify_keys(Queued::SCREEN)
|
|
230
|
+
end.to_not raise_error
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe '#flush' do
|
|
235
|
+
it 'waits for the queue to finish on a flush' do
|
|
236
|
+
client.identify Queued::IDENTIFY
|
|
237
|
+
client.track Queued::TRACK
|
|
238
|
+
client.flush
|
|
239
|
+
|
|
240
|
+
expect(client.queued_messages).to eq(0)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'completes when the process forks' do
|
|
244
|
+
client.identify Queued::IDENTIFY
|
|
245
|
+
|
|
246
|
+
Process.fork do
|
|
247
|
+
client.track Queued::TRACK
|
|
248
|
+
client.flush
|
|
249
|
+
expect(client.queued_messages).to eq(0)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
Process.wait
|
|
253
|
+
end unless defined? JRUBY_VERSION
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
context 'common' do
|
|
257
|
+
check_property = proc { |msg, k, v| msg[k] && msg[k] == v }
|
|
258
|
+
|
|
259
|
+
let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => "coco barked", :name => "coco" } }
|
|
260
|
+
|
|
261
|
+
it 'does not convert ids given as fixnums to strings' do
|
|
262
|
+
[:track, :screen, :page, :identify].each do |s|
|
|
263
|
+
client.send(s, data)
|
|
264
|
+
message = queue.pop(true)
|
|
265
|
+
|
|
266
|
+
expect(check_property.call(message, :userId, 1)).to eq(true)
|
|
267
|
+
expect(check_property.call(message, :anonymousId, 4)).to eq(true)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'converts message id to string' do
|
|
272
|
+
[:track, :screen, :page, :group, :identify, :alias].each do |s|
|
|
273
|
+
client.send(s, data)
|
|
274
|
+
message = queue.pop(true)
|
|
275
|
+
|
|
276
|
+
expect(check_property.call(message, :messageId, '5')).to eq(true)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
context 'group' do
|
|
281
|
+
it 'does not convert ids given as fixnums to strings' do
|
|
282
|
+
client.group(data)
|
|
283
|
+
message = queue.pop(true)
|
|
284
|
+
|
|
285
|
+
expect(check_property.call(message, :userId, 1)).to eq(true)
|
|
286
|
+
expect(check_property.call(message, :groupId, 2)).to eq(true)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
context 'alias' do
|
|
291
|
+
it 'does not convert ids given as fixnums to strings' do
|
|
292
|
+
client.alias(data)
|
|
293
|
+
message = queue.pop(true)
|
|
294
|
+
|
|
295
|
+
expect(check_property.call(message, :userId, 1)).to eq(true)
|
|
296
|
+
expect(check_property.call(message, :previousId, 3)).to eq(true)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it 'sends integrations' do
|
|
301
|
+
[:track, :screen, :page, :group, :identify, :alias].each do |s|
|
|
302
|
+
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"
|
|
303
|
+
message = queue.pop(true)
|
|
304
|
+
expect(message[:integrations][:All]).to eq(true)
|
|
305
|
+
expect(message[:integrations][:Salesforce]).to eq(false)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|