Skip to content
This repository was archived by the owner on Feb 28, 2023. It is now read-only.

Commit d74acb9

Browse files
committed
Add support for timestamp and idempotency key
1 parent 0f0a353 commit d74acb9

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

lib/heap/client.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ def add_user_properties(identity, properties)
106106
# each value must be a Number or String with fewer than 1024 characters
107107
# @return [HeapAPI::Client] self
108108
# @see https://heapanalytics.com/docs/server-side#track
109-
def track(event, identity, properties = nil)
109+
def track(event, identity, properties = nil, options = {})
110+
options ||= {}
110111
ensure_valid_app_id!
111112

112113
event_name = event.to_s
@@ -123,6 +124,14 @@ def track(event, identity, properties = nil)
123124
ensure_valid_properties! properties
124125
end
125126

127+
unless options[:timestamp].nil?
128+
body[:timestamp] = ensure_valid_timestamp!(options[:timestamp])
129+
end
130+
131+
unless options[:idempotency_key].nil?
132+
body[:idempotency_key] = ensure_valid_idempotency_key!(options[:idempotency_key])
133+
end
134+
126135
response = connection.post '/api/track', body,
127136
'User-Agent' => user_agent
128137
raise HeapAPI::ApiError.new(response) unless response.success?

lib/heap/validations.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Internal methods used to validate API input.
2+
require 'date'
23

34
class HeapAPI::Client
45
# Makes sure that the client's app_id property is set.
@@ -43,6 +44,45 @@ def ensure_valid_identity!(identity)
4344
end
4445
private :ensure_valid_identity!
4546

47+
# Validates timestamp, making sure it's a valid iso8061 timestamp or
48+
# number of milliseconds since epoch
49+
#
50+
# @param [String|Integer|DateTime|Time] timestamp
51+
# @raise ArgumentError if timestamp is of an invalid type
52+
# @return [String] unix epoch milliseconds or iso8061
53+
def ensure_valid_timestamp!(timestamp)
54+
if timestamp.kind_of?(Time)
55+
timestamp = timestamp.to_datetime
56+
end
57+
if timestamp.kind_of?(DateTime)
58+
timestamp = timestamp.strftime('%Q').to_i
59+
end
60+
if timestamp.kind_of?(String) && iso8601?(timestamp)
61+
timestamp
62+
elsif timestamp.kind_of?(Integer)
63+
timestamp.to_s
64+
else
65+
raise ArgumentError,
66+
"Unsupported timestamp format #{timestamp}. " +
67+
"Must be iso8601 or unix epoch milliseconds."
68+
end
69+
end
70+
private :ensure_valid_timestamp!
71+
72+
# Validate idempotency_key, making sure it's a string
73+
#
74+
# @param [String|Integer] idempotency_key
75+
# @raise ArgumentError if identity is of an invalid type or too long.
76+
# @return [String] stringified idempotency_key
77+
def ensure_valid_idempotency_key!(idempotency_key)
78+
unless idempotency_key.kind_of?(String) || idempotency_key.kind_of?(Integer)
79+
raise ArgumentError, "Unsupported idempotency key format for " +
80+
"#{idempotency_key}. Must be string or integer"
81+
end
82+
idempotency_key.to_s
83+
end
84+
private :ensure_valid_idempotency_key!
85+
4686
# Validates a bag of properties sent to a Heap server-side API.
4787
#
4888
# @param [Hash<String, String|Number>] properties key-value property bag;
@@ -74,4 +114,12 @@ def ensure_valid_properties!(properties)
74114
end
75115
end
76116
private :ensure_valid_properties!
117+
118+
def iso8601?(string)
119+
Time.iso8601(string)
120+
true
121+
rescue ArgumentError
122+
false
123+
end
124+
private :iso8601?
77125
end

test/client_track_test.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,33 @@ def test_track_with_array_property_value
121121
exception.message
122122
end
123123

124+
def test_track_with_non_date_timestamp
125+
exception = assert_raises ArgumentError do
126+
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :timestamp => 'foobar'
127+
end
128+
assert_equal ArgumentError, exception.class
129+
assert_equal 'Unsupported timestamp format foobar. Must be iso8601 or unix epoch milliseconds.',
130+
exception.message
131+
end
132+
133+
def test_track_with_array_timestamp
134+
exception = assert_raises ArgumentError do
135+
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :timestamp => []
136+
end
137+
assert_equal ArgumentError, exception.class
138+
assert_equal 'Unsupported timestamp format []. Must be iso8601 or unix epoch milliseconds.',
139+
exception.message
140+
end
141+
142+
def test_track_with_array_idempotency_key
143+
exception = assert_raises ArgumentError do
144+
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :idempotency_key => []
145+
end
146+
assert_equal ArgumentError, exception.class
147+
assert_equal 'Unsupported idempotency key format for []. Must be string or integer',
148+
exception.message
149+
end
150+
124151
def test_track
125152
@stubs.post '/api/track' do |env|
126153
golden_body = {
@@ -174,6 +201,67 @@ def test_track_with_properties
174201
'test-identity','foo' => 'bar', :heap => :hurray)
175202
end
176203

204+
def test_track_with_timestamp
205+
@stubs.post '/api/track' do |env|
206+
golden_body = {
207+
'app_id' => 'test-app-id',
208+
'identity' => 'test-identity',
209+
'event' => 'test_track_with_timestamp',
210+
'properties' => {},
211+
'timestamp' => '1524038400000'
212+
}
213+
assert_equal 'application/json', env[:request_headers]['Content-Type']
214+
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
215+
assert_equal golden_body, JSON.parse(env[:body])
216+
217+
[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
218+
end
219+
220+
assert_equal @heap, @heap.track('test_track_with_timestamp',
221+
'test-identity', {}, :timestamp => Time.parse("2018-04-18 08:00:00 UTC"))
222+
end
223+
224+
def test_track_with_iso8601_timestamp
225+
timestamp = "2018-04-18T22:42:38+03:00"
226+
@stubs.post '/api/track' do |env|
227+
golden_body = {
228+
'app_id' => 'test-app-id',
229+
'identity' => 'test-identity',
230+
'event' => 'test_track_with_iso8601_timestamp',
231+
'properties' => {},
232+
'timestamp' => timestamp
233+
}
234+
assert_equal 'application/json', env[:request_headers]['Content-Type']
235+
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
236+
assert_equal golden_body, JSON.parse(env[:body])
237+
238+
[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
239+
end
240+
241+
assert_equal @heap, @heap.track('test_track_with_iso8601_timestamp',
242+
'test-identity', {}, :timestamp => timestamp)
243+
end
244+
245+
def test_track_with_idempotency_key
246+
@stubs.post '/api/track' do |env|
247+
golden_body = {
248+
'app_id' => 'test-app-id',
249+
'identity' => 'test-identity',
250+
'event' => 'test_track_with_idempotency_key',
251+
'properties' => {},
252+
'idempotency_key' => 'foobar35214532512'
253+
}
254+
assert_equal 'application/json', env[:request_headers]['Content-Type']
255+
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
256+
assert_equal golden_body, JSON.parse(env[:body])
257+
258+
[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
259+
end
260+
261+
assert_equal @heap, @heap.track('test_track_with_idempotency_key',
262+
'test-identity', {}, :idempotency_key => 'foobar35214532512')
263+
end
264+
177265
def test_track_error
178266
@stubs.post '/api/track' do |env|
179267
[400, { 'Content-Type' => 'text/plain; encoding=utf8' }, 'Bad request']

0 commit comments

Comments
 (0)