keen 0.4.3 → 0.4.4
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.
- data/README.md +14 -6
- data/keen.gemspec +1 -1
- data/lib/keen/client.rb +40 -18
- data/lib/keen/version.rb +1 -1
- data/spec/integration/api_spec.rb +10 -10
- data/spec/keen/client_spec.rb +60 -8
- data/spec/spec_helper.rb +8 -2
- data/spec/synchrony/synchrony_spec.rb +1 -1
- metadata +3 -3
data/README.md
CHANGED
@@ -89,8 +89,7 @@ To configure keen-gem credentials in code, do as follows:
|
|
89
89
|
|
90
90
|
You can also configure individual client instances as follows:
|
91
91
|
|
92
|
-
keen = Keen::Client.new(:project_id => 'your-project-id',
|
93
|
-
:api_key => 'your-api-key')
|
92
|
+
keen = Keen::Client.new(:project_id => 'your-project-id', :api_key => 'your-api-key')
|
94
93
|
|
95
94
|
#### em-synchrony
|
96
95
|
keen-gem can be used with [em-synchrony](https://github.com/igrigorik/em-synchrony).
|
@@ -106,15 +105,24 @@ This is useful for situations like tracking email opens using [image beacons](ht
|
|
106
105
|
In this situation, the JSON event data is passed by encoding it base-64 and adding it as a request parameter called `data`.
|
107
106
|
The `beacon_url` method found on the `Keen::Client` does this for you. Here's an example:
|
108
107
|
|
109
|
-
keen = Keen::Client.new(:project_id => '12345',
|
110
|
-
:api_key => 'abcde')
|
108
|
+
keen = Keen::Client.new(:project_id => '12345', :api_key => 'abcde')
|
111
109
|
|
112
110
|
keen.beacon_url("sign_ups", :recipient => "foo@foo.com")
|
113
|
-
# => "https://api.keen.io/3.0/projects/
|
114
|
-
email_opens?api_key=f806128f31c349fda124b62d1f4cf4b2&data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9"
|
111
|
+
# => "https://api.keen.io/3.0/projects/12345/events/email_opens?api_key=abcde&data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9"
|
115
112
|
|
116
113
|
To track email opens, simply add an image to your email template that points to this URL.
|
117
114
|
|
115
|
+
### Changelog
|
116
|
+
|
117
|
+
##### 0.4.4
|
118
|
+
+ Event collections are URI escaped to account for spaces.
|
119
|
+
+ User agent of API calls made more granular to aid in support cases.
|
120
|
+
+ Throw arguments error for nil event_collection and properties arguments.
|
121
|
+
|
122
|
+
##### 0.4.3
|
123
|
+
+ Added beacon_url support
|
124
|
+
+ Add support for using em-synchrony with asynchronous calls
|
125
|
+
|
118
126
|
### Questions & Support
|
119
127
|
|
120
128
|
If you have any questions, bugs, or suggestions, please
|
data/keen.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = "josh@keen.io"
|
11
11
|
s.homepage = "https://github.com/keenlabs/keen-gem"
|
12
12
|
s.summary = "Keen IO API Client"
|
13
|
-
s.description = "
|
13
|
+
s.description = "Send events and build analytics features into your Ruby applications."
|
14
14
|
|
15
15
|
s.add_dependency "multi_json", "~> 1.0"
|
16
16
|
s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
|
data/lib/keen/client.rb
CHANGED
@@ -3,6 +3,7 @@ require 'keen/version'
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'multi_json'
|
5
5
|
require 'base64'
|
6
|
+
require 'uri'
|
6
7
|
|
7
8
|
module Keen
|
8
9
|
class Client
|
@@ -18,16 +19,21 @@ module Keen
|
|
18
19
|
:verify_depth => 5,
|
19
20
|
:ca_file => File.expand_path("../../../config/cacert.pem", __FILE__) },
|
20
21
|
:api_async_http_options => {},
|
21
|
-
:api_headers => {
|
22
|
-
"
|
23
|
-
|
22
|
+
:api_headers => lambda { |sync_or_async|
|
23
|
+
user_agent = "keen-gem, v#{Keen::VERSION}, #{sync_or_async}"
|
24
|
+
user_agent += ", #{RUBY_VERSION}, #{RUBY_PLATFORM}, #{RUBY_PATCHLEVEL}"
|
25
|
+
if defined?(RUBY_ENGINE)
|
26
|
+
user_agent += ", #{RUBY_ENGINE}"
|
27
|
+
end
|
28
|
+
{ "Content-Type" => "application/json",
|
29
|
+
"User-Agent" => user_agent }
|
24
30
|
}
|
25
31
|
}
|
26
32
|
|
27
|
-
def beacon_url(
|
33
|
+
def beacon_url(event_collection, properties)
|
28
34
|
json = MultiJson.encode(properties)
|
29
35
|
data = [json].pack("m0").tr("+/", "-_").gsub("\n", "")
|
30
|
-
"https://#{api_host}
|
36
|
+
"https://#{api_host}#{api_path(event_collection)}?api_key=#{@api_key}&data=#{data}"
|
31
37
|
end
|
32
38
|
|
33
39
|
def initialize(*args)
|
@@ -44,13 +50,15 @@ module Keen
|
|
44
50
|
:project_id, :api_key)
|
45
51
|
end
|
46
52
|
|
47
|
-
def publish(
|
53
|
+
def publish(event_collection, properties)
|
48
54
|
check_configuration!
|
55
|
+
check_event_data!(event_collection, properties)
|
56
|
+
|
49
57
|
begin
|
50
58
|
response = Keen::HTTP::Sync.new(
|
51
59
|
api_host, api_port, api_sync_http_options).post(
|
52
|
-
:path => api_path(
|
53
|
-
:headers => api_headers_with_auth,
|
60
|
+
:path => api_path(event_collection),
|
61
|
+
:headers => api_headers_with_auth("sync"),
|
54
62
|
:body => MultiJson.encode(properties))
|
55
63
|
rescue Exception => http_error
|
56
64
|
raise HttpError.new("Couldn't connect to Keen IO: #{http_error.message}", http_error)
|
@@ -58,15 +66,16 @@ module Keen
|
|
58
66
|
process_response(response.code, response.body.chomp)
|
59
67
|
end
|
60
68
|
|
61
|
-
def publish_async(
|
69
|
+
def publish_async(event_collection, properties)
|
62
70
|
check_configuration!
|
71
|
+
check_event_data!(event_collection, properties)
|
63
72
|
|
64
73
|
deferrable = EventMachine::DefaultDeferrable.new
|
65
74
|
|
66
75
|
http_client = Keen::HTTP::Async.new(api_host, api_port, api_async_http_options)
|
67
76
|
http = http_client.post({
|
68
|
-
:path => api_path(
|
69
|
-
:headers => api_headers_with_auth,
|
77
|
+
:path => api_path(event_collection),
|
78
|
+
:headers => api_headers_with_auth("async"),
|
70
79
|
:body => MultiJson.encode(properties)
|
71
80
|
})
|
72
81
|
|
@@ -95,8 +104,8 @@ module Keen
|
|
95
104
|
end
|
96
105
|
|
97
106
|
# deprecated
|
98
|
-
def add_event(
|
99
|
-
self.publish(
|
107
|
+
def add_event(event_collection, properties, options={})
|
108
|
+
self.publish(event_collection, properties, options)
|
100
109
|
end
|
101
110
|
|
102
111
|
private
|
@@ -117,12 +126,12 @@ module Keen
|
|
117
126
|
end
|
118
127
|
end
|
119
128
|
|
120
|
-
def api_path(
|
121
|
-
"/#{api_version}/projects/#{project_id}/events/#{
|
129
|
+
def api_path(event_collection)
|
130
|
+
"/#{api_version}/projects/#{project_id}/events/#{URI.escape(event_collection)}"
|
122
131
|
end
|
123
132
|
|
124
|
-
def api_headers_with_auth
|
125
|
-
api_headers.merge("Authorization" => api_key)
|
133
|
+
def api_headers_with_auth(sync_or_async)
|
134
|
+
api_headers(sync_or_async).merge("Authorization" => api_key)
|
126
135
|
end
|
127
136
|
|
128
137
|
def check_configuration!
|
@@ -130,8 +139,21 @@ module Keen
|
|
130
139
|
raise ConfigurationError, "API Key must be set" unless api_key
|
131
140
|
end
|
132
141
|
|
142
|
+
def check_event_data!(event_collection, properties)
|
143
|
+
raise ArgumentError, "Event collection can not be nil" unless event_collection
|
144
|
+
raise ArgumentError, "Event properties can not be nil" unless properties
|
145
|
+
end
|
146
|
+
|
133
147
|
def method_missing(_method, *args, &block)
|
134
|
-
CONFIG[_method.to_sym]
|
148
|
+
if config = CONFIG[_method.to_sym]
|
149
|
+
if config.is_a?(Proc)
|
150
|
+
config.call(*args)
|
151
|
+
else
|
152
|
+
config
|
153
|
+
end
|
154
|
+
else
|
155
|
+
super
|
156
|
+
end
|
135
157
|
end
|
136
158
|
end
|
137
159
|
end
|
data/lib/keen/version.rb
CHANGED
@@ -32,16 +32,8 @@ describe "Keen IO API" do
|
|
32
32
|
}.to raise_error(Keen::AuthenticationError)
|
33
33
|
end
|
34
34
|
|
35
|
-
it "should
|
36
|
-
|
37
|
-
Keen.publish(collection, nil)
|
38
|
-
}.to raise_error(Keen::BadRequestError)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should return not found for an invalid collection name" do
|
42
|
-
expect {
|
43
|
-
Keen.publish(nil, event_properties)
|
44
|
-
}.to raise_error(Keen::NotFoundError)
|
35
|
+
it "should success if a non-url-safe event collection is specified" do
|
36
|
+
Keen.publish("infinite possibilities", event_properties).should == api_success
|
45
37
|
end
|
46
38
|
end
|
47
39
|
|
@@ -58,6 +50,14 @@ describe "Keen IO API" do
|
|
58
50
|
}
|
59
51
|
end
|
60
52
|
|
53
|
+
it "should publish to non-url-safe collections" do
|
54
|
+
EM.run {
|
55
|
+
Keen.publish_async("foo bar", event_properties).callback { |response|
|
56
|
+
response.should == api_success
|
57
|
+
EM.stop
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
data/spec/keen/client_spec.rb
CHANGED
@@ -56,7 +56,7 @@ describe Keen::Client do
|
|
56
56
|
it "should post using the collection and properties" do
|
57
57
|
stub_api(api_url(collection), 201, "")
|
58
58
|
@client.publish(collection, event_properties)
|
59
|
-
expect_post(api_url(collection), event_properties, api_key)
|
59
|
+
expect_post(api_url(collection), event_properties, api_key, "sync")
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should return the proper response" do
|
@@ -65,6 +65,24 @@ describe Keen::Client do
|
|
65
65
|
@client.publish(collection, event_properties).should == api_response
|
66
66
|
end
|
67
67
|
|
68
|
+
it "should raise an argument error if no event collection is specified" do
|
69
|
+
expect {
|
70
|
+
@client.publish(nil, {})
|
71
|
+
}.to raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should raise an argument error if no properties are specified" do
|
75
|
+
expect {
|
76
|
+
@client.publish(collection, nil)
|
77
|
+
}.to raise_error(ArgumentError)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should url encode the event collection" do
|
81
|
+
stub_api(api_url("foo%20bar"), 201, "")
|
82
|
+
@client.publish("foo bar", event_properties)
|
83
|
+
expect_post(api_url("foo%20bar"), event_properties, api_key, "sync")
|
84
|
+
end
|
85
|
+
|
68
86
|
it "should wrap exceptions" do
|
69
87
|
stub_request(:post, api_url(collection)).to_timeout
|
70
88
|
e = nil
|
@@ -94,19 +112,50 @@ describe Keen::Client do
|
|
94
112
|
stub_api(api_url(collection), 201, api_success)
|
95
113
|
EM.run {
|
96
114
|
@client.publish_async(collection, event_properties).callback {
|
97
|
-
|
98
|
-
|
115
|
+
begin
|
116
|
+
expect_post(api_url(collection), event_properties, api_key, "async")
|
117
|
+
ensure
|
118
|
+
EM.stop
|
119
|
+
end
|
99
120
|
}
|
100
121
|
}
|
101
122
|
end
|
102
123
|
|
124
|
+
it "should uri encode the event collection" do
|
125
|
+
stub_api(api_url("foo%20bar"), 201, api_success)
|
126
|
+
EM.run {
|
127
|
+
@client.publish_async("foo bar", event_properties).callback {
|
128
|
+
begin
|
129
|
+
expect_post(api_url("foo%20bar"), event_properties, api_key, "async")
|
130
|
+
ensure
|
131
|
+
EM.stop
|
132
|
+
end
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should raise an argument error if no event collection is specified" do
|
138
|
+
expect {
|
139
|
+
@client.publish_async(nil, {})
|
140
|
+
}.to raise_error(ArgumentError)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should raise an argument error if no properties are specified" do
|
144
|
+
expect {
|
145
|
+
@client.publish_async(collection, nil)
|
146
|
+
}.to raise_error(ArgumentError)
|
147
|
+
end
|
148
|
+
|
103
149
|
describe "deferrable callbacks" do
|
104
150
|
it "should trigger callbacks" do
|
105
151
|
stub_api(api_url(collection), 201, api_success)
|
106
152
|
EM.run {
|
107
153
|
@client.publish_async(collection, event_properties).callback { |response|
|
108
|
-
|
109
|
-
|
154
|
+
begin
|
155
|
+
response.should == api_success
|
156
|
+
ensure
|
157
|
+
EM.stop
|
158
|
+
end
|
110
159
|
}
|
111
160
|
}
|
112
161
|
end
|
@@ -115,9 +164,12 @@ describe Keen::Client do
|
|
115
164
|
stub_request(:post, api_url(collection)).to_timeout
|
116
165
|
EM.run {
|
117
166
|
@client.publish_async(collection, event_properties).errback { |error|
|
118
|
-
|
119
|
-
|
120
|
-
|
167
|
+
begin
|
168
|
+
error.should_not be_nil
|
169
|
+
error.message.should == "Couldn't connect to Keen IO: WebMock timeout error"
|
170
|
+
ensure
|
171
|
+
EM.stop
|
172
|
+
end
|
121
173
|
}
|
122
174
|
}
|
123
175
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -17,11 +17,17 @@ module Keen::SpecHelpers
|
|
17
17
|
:body => MultiJson.encode(json_body))
|
18
18
|
end
|
19
19
|
|
20
|
-
def expect_post(url, event_properties, api_key)
|
20
|
+
def expect_post(url, event_properties, api_key, sync_or_async_ua)
|
21
|
+
user_agent = "keen-gem, v#{Keen::VERSION}, #{sync_or_async_ua}"
|
22
|
+
user_agent += ", #{RUBY_VERSION}, #{RUBY_PLATFORM}, #{RUBY_PATCHLEVEL}"
|
23
|
+
if defined?(RUBY_ENGINE)
|
24
|
+
user_agent += ", #{RUBY_ENGINE}"
|
25
|
+
end
|
26
|
+
|
21
27
|
WebMock.should have_requested(:post, url).with(
|
22
28
|
:body => MultiJson.encode(event_properties),
|
23
29
|
:headers => { "Content-Type" => "application/json",
|
24
|
-
"User-Agent" =>
|
30
|
+
"User-Agent" => user_agent,
|
25
31
|
"Authorization" => api_key })
|
26
32
|
end
|
27
33
|
|
@@ -19,7 +19,7 @@ describe Keen::HTTP::Async do
|
|
19
19
|
stub_api(api_url(collection), 201, api_success)
|
20
20
|
EM.synchrony {
|
21
21
|
@client.publish_async(collection, event_properties)
|
22
|
-
expect_post(api_url(collection), event_properties, api_key)
|
22
|
+
expect_post(api_url(collection), event_properties, api_key, "async")
|
23
23
|
EM.stop
|
24
24
|
}
|
25
25
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-01-
|
13
|
+
date: 2013-01-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: multi_json
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
- - ~>
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: '1.0'
|
31
|
-
description:
|
31
|
+
description: Send events and build analytics features into your Ruby applications.
|
32
32
|
email: josh@keen.io
|
33
33
|
executables: []
|
34
34
|
extensions: []
|