keen 0.5.0 → 0.6.0
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/.travis.yml +1 -0
- data/README.md +61 -11
- data/lib/keen.rb +2 -1
- data/lib/keen/client.rb +10 -77
- data/lib/keen/client/publishing_methods.rb +111 -0
- data/lib/keen/client/querying_methods.rb +199 -0
- data/lib/keen/http.rb +6 -0
- data/lib/keen/version.rb +1 -1
- data/spec/integration/api_spec.rb +112 -34
- data/spec/keen/client/publishing_methods_spec.rb +161 -0
- data/spec/keen/client/querying_methods_spec.rb +132 -0
- data/spec/keen/client_spec.rb +22 -188
- data/spec/spec_helper.rb +23 -8
- data/spec/synchrony/synchrony_spec.rb +4 -4
- metadata +17 -7
- checksums.yaml +0 -7
data/lib/keen/http.rb
CHANGED
data/lib/keen/version.rb
CHANGED
@@ -3,51 +3,129 @@ require File.expand_path("../spec_helper", __FILE__)
|
|
3
3
|
describe "Keen IO API" do
|
4
4
|
let(:project_id) { ENV['KEEN_PROJECT_ID'] }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
describe "publishing" do
|
7
|
+
let(:collection) { "users" }
|
8
|
+
let(:event_properties) { { "name" => "Bob" } }
|
9
|
+
let(:api_success) { { "created" => true } }
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
describe "success" do
|
12
|
+
it "should return a created status for a valid post" do
|
13
|
+
Keen.publish(collection, event_properties).should == api_success
|
14
|
+
end
|
14
15
|
end
|
15
|
-
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
describe "failure" do
|
18
|
+
it "should raise a not found error if an invalid project id" do
|
19
|
+
client = Keen::Client.new(:project_id => "riker")
|
20
|
+
expect {
|
21
|
+
client.publish(collection, event_properties)
|
22
|
+
}.to raise_error(Keen::NotFoundError)
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
it "should succeed if a non-url-safe event collection is specified" do
|
26
|
+
Keen.publish("infinite possibilities", event_properties).should == api_success
|
27
|
+
end
|
27
28
|
end
|
28
|
-
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
describe "async" do
|
31
|
+
# no TLS support in EventMachine on jRuby
|
32
|
+
unless defined?(JRUBY_VERSION)
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
it "should publish the event and trigger callbacks" do
|
35
|
+
EM.run {
|
36
|
+
Keen.publish_async(collection, event_properties).callback { |response|
|
37
|
+
response.should == api_success
|
38
|
+
EM.stop
|
39
|
+
}
|
39
40
|
}
|
40
|
-
|
41
|
-
end
|
41
|
+
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
it "should publish to non-url-safe collections" do
|
44
|
+
EM.run {
|
45
|
+
Keen.publish_async("foo bar", event_properties).callback { |response|
|
46
|
+
response.should == api_success
|
47
|
+
EM.stop
|
48
|
+
}
|
48
49
|
}
|
49
|
-
|
50
|
+
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
54
|
+
|
55
|
+
describe "queries" do
|
56
|
+
let(:api_key) { ENV['KEEN_API_KEY'] }
|
57
|
+
let(:event_collection) { "purchases_" + rand(100000).to_s }
|
58
|
+
let(:returns_event_collection) { "returns_" + rand(100000).to_s }
|
59
|
+
|
60
|
+
before(:all) do
|
61
|
+
Keen.publish(event_collection, {
|
62
|
+
:username => "bob",
|
63
|
+
:price => 10
|
64
|
+
})
|
65
|
+
Keen.publish(event_collection, {
|
66
|
+
:username => "ted",
|
67
|
+
:price => 20
|
68
|
+
})
|
69
|
+
Keen.publish(returns_event_collection, {
|
70
|
+
:username => "bob",
|
71
|
+
:price => 30
|
72
|
+
})
|
73
|
+
sleep(1)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return a valid count" do
|
77
|
+
Keen.count(event_collection).should == 2
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return a valid count_unique" do
|
81
|
+
Keen.count_unique(event_collection, :target_property => "price").should == 2
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should return a valid sum" do
|
85
|
+
Keen.sum(event_collection, :target_property => "price").should == 30
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return a valid minimum" do
|
89
|
+
Keen.minimum(event_collection, :target_property => "price").should == 10
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return a valid maximum" do
|
93
|
+
Keen.maximum(event_collection, :target_property => "price").should == 20
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return a valid average" do
|
97
|
+
Keen.average(event_collection, :target_property => "price").should == 15
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return a valid select_unique" do
|
101
|
+
results = Keen.select_unique(event_collection, :target_property => "price")
|
102
|
+
results.sort.should == [10, 20].sort
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return a valid extraction" do
|
106
|
+
results = Keen.extraction(event_collection)
|
107
|
+
results.length.should == 2
|
108
|
+
results.all? { |result| result["keen"] }.should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return a valid funnel" do
|
112
|
+
steps = [{
|
113
|
+
:event_collection => event_collection,
|
114
|
+
:actor_property => "username"
|
115
|
+
}, {
|
116
|
+
:event_collection => returns_event_collection,
|
117
|
+
:actor_property => "username"
|
118
|
+
}]
|
119
|
+
results = Keen.funnel(:steps => steps)
|
120
|
+
results.should == [2, 1]
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should apply filters" do
|
124
|
+
Keen.count(event_collection, :filters => [{
|
125
|
+
:property_name => "username",
|
126
|
+
:operator => "eq",
|
127
|
+
:property_value => "ted"
|
128
|
+
}]).should == 1
|
129
|
+
end
|
130
|
+
end
|
53
131
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Keen::Client::PublishingMethods do
|
4
|
+
let(:project_id) { "12345" }
|
5
|
+
let(:api_host) { "api.keen.io" }
|
6
|
+
let(:collection) { "users" }
|
7
|
+
let(:event_properties) { { "name" => "Bob" } }
|
8
|
+
let(:api_success) { { "created" => true } }
|
9
|
+
let(:client) { Keen::Client.new(:project_id => project_id) }
|
10
|
+
|
11
|
+
describe "publish" do
|
12
|
+
it "should post using the collection and properties" do
|
13
|
+
stub_keen_post(api_event_resource_url(collection), 201, "")
|
14
|
+
client.publish(collection, event_properties)
|
15
|
+
expect_keen_post(api_event_resource_url(collection), event_properties, "sync")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return the proper response" do
|
19
|
+
api_response = { "created" => true }
|
20
|
+
stub_keen_post(api_event_resource_url(collection), 201, api_response)
|
21
|
+
client.publish(collection, event_properties).should == api_response
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise an argument error if no event collection is specified" do
|
25
|
+
expect {
|
26
|
+
client.publish(nil, {})
|
27
|
+
}.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise an argument error if no properties are specified" do
|
31
|
+
expect {
|
32
|
+
client.publish(collection, nil)
|
33
|
+
}.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should url encode the event collection" do
|
37
|
+
stub_keen_post(api_event_resource_url("foo%20bar"), 201, "")
|
38
|
+
client.publish("foo bar", event_properties)
|
39
|
+
expect_keen_post(api_event_resource_url("foo%20bar"), event_properties, "sync")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should wrap exceptions" do
|
43
|
+
stub_request(:post, api_event_resource_url(collection)).to_timeout
|
44
|
+
e = nil
|
45
|
+
begin
|
46
|
+
client.publish(collection, event_properties)
|
47
|
+
rescue Exception => exception
|
48
|
+
e = exception
|
49
|
+
end
|
50
|
+
|
51
|
+
e.class.should == Keen::HttpError
|
52
|
+
e.original_error.class.should == Timeout::Error
|
53
|
+
e.message.should == "Couldn't connect to Keen IO: execution expired"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise an exception if client has no project_id" do
|
57
|
+
expect {
|
58
|
+
Keen::Client.new.publish(collection, event_properties)
|
59
|
+
}.to raise_error(Keen::ConfigurationError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "publish_async" do
|
64
|
+
# no TLS support in EventMachine on jRuby
|
65
|
+
unless defined?(JRUBY_VERSION)
|
66
|
+
it "should require a running event loop" do
|
67
|
+
expect {
|
68
|
+
client.publish_async(collection, event_properties)
|
69
|
+
}.to raise_error(Keen::Error)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should post the event data" do
|
73
|
+
stub_keen_post(api_event_resource_url(collection), 201, api_success)
|
74
|
+
EM.run {
|
75
|
+
client.publish_async(collection, event_properties).callback {
|
76
|
+
begin
|
77
|
+
expect_keen_post(api_event_resource_url(collection), event_properties, "async")
|
78
|
+
ensure
|
79
|
+
EM.stop
|
80
|
+
end
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should uri encode the event collection" do
|
86
|
+
stub_keen_post(api_event_resource_url("foo%20bar"), 201, api_success)
|
87
|
+
EM.run {
|
88
|
+
client.publish_async("foo bar", event_properties).callback {
|
89
|
+
begin
|
90
|
+
expect_post(api_event_resource_url("foo%20bar"), event_properties, "async")
|
91
|
+
ensure
|
92
|
+
EM.stop
|
93
|
+
end
|
94
|
+
}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should raise an argument error if no event collection is specified" do
|
99
|
+
expect {
|
100
|
+
client.publish_async(nil, {})
|
101
|
+
}.to raise_error(ArgumentError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should raise an argument error if no properties are specified" do
|
105
|
+
expect {
|
106
|
+
client.publish_async(collection, nil)
|
107
|
+
}.to raise_error(ArgumentError)
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "deferrable callbacks" do
|
111
|
+
it "should trigger callbacks" do
|
112
|
+
stub_keen_post(api_event_resource_url(collection), 201, api_success)
|
113
|
+
EM.run {
|
114
|
+
client.publish_async(collection, event_properties).callback { |response|
|
115
|
+
begin
|
116
|
+
response.should == api_success
|
117
|
+
ensure
|
118
|
+
EM.stop
|
119
|
+
end
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should trigger errbacks" do
|
125
|
+
stub_request(:post, api_event_resource_url(collection)).to_timeout
|
126
|
+
EM.run {
|
127
|
+
client.publish_async(collection, event_properties).errback { |error|
|
128
|
+
begin
|
129
|
+
error.should_not be_nil
|
130
|
+
error.message.should == "Couldn't connect to Keen IO: WebMock timeout error"
|
131
|
+
ensure
|
132
|
+
EM.stop
|
133
|
+
end
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should raise an exception if client has no project_id" do
|
142
|
+
expect {
|
143
|
+
Keen::Client.new.publish_async(collection, event_properties)
|
144
|
+
}.to raise_error(Keen::ConfigurationError)
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#add_event" do
|
148
|
+
it "should alias to publish" do
|
149
|
+
client.should_receive(:publish).with("users", {:a => 1}, {:b => 2})
|
150
|
+
client.add_event("users", {:a => 1}, {:b => 2})
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "beacon_url" do
|
155
|
+
it "should return a url with a base-64 encoded json param" do
|
156
|
+
client = Keen::Client.new(project_id)
|
157
|
+
client.beacon_url("sign_ups", { :name => "Bob" }).should ==
|
158
|
+
"https://api.keen.io/3.0/projects/12345/events/sign_ups?data=eyJuYW1lIjoiQm9iIn0="
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Keen::Client do
|
4
|
+
let(:project_id) { "12345" }
|
5
|
+
let(:api_key) { "abcde" }
|
6
|
+
let(:api_host) { "api.keen.io" }
|
7
|
+
let(:api_version) { "3.0" }
|
8
|
+
let(:event_collection) { "users" }
|
9
|
+
let(:client) { Keen::Client.new(:project_id => project_id, :api_key => api_key) }
|
10
|
+
|
11
|
+
def query_url(query_name, query_params)
|
12
|
+
"https://#{api_host}/#{api_version}/projects/#{project_id}/queries/#{query_name}#{query_params}"
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "querying names" do
|
16
|
+
let(:params) { { :event_collection => "signups" } }
|
17
|
+
|
18
|
+
["minimum", "maximum", "sum", "average", "count", "count_unique", "select_unique", "extraction"].each do |query_name|
|
19
|
+
it "should call keen query passing the query name" do
|
20
|
+
client.should_receive(:query).with(query_name.to_sym, event_collection, params)
|
21
|
+
client.send(query_name, event_collection, params)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "funnel" do
|
26
|
+
it "should call keen query w/o event collection" do
|
27
|
+
client.should_receive(:query).with(:funnel, nil, params)
|
28
|
+
client.funnel(params)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#query" do
|
34
|
+
describe "with an improperly configured client" do
|
35
|
+
it "should require a project id" do
|
36
|
+
expect {
|
37
|
+
Keen::Client.new(:api_key => api_key).count("users", {})
|
38
|
+
}.to raise_error(Keen::ConfigurationError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should require an api key" do
|
42
|
+
expect {
|
43
|
+
Keen::Client.new(:project_id => project_id).count("users", {})
|
44
|
+
}.to raise_error(Keen::ConfigurationError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with a valid client" do
|
49
|
+
let(:query) { client.method(:query) }
|
50
|
+
let(:query_name) { "count" }
|
51
|
+
let(:api_response) { { "result" => 1 } }
|
52
|
+
|
53
|
+
def test_query(extra_query_params="", extra_query_hash={})
|
54
|
+
expected_query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
|
55
|
+
expected_query_params += extra_query_params
|
56
|
+
expected_url = query_url(query_name, expected_query_params)
|
57
|
+
stub_keen_get(expected_url, 200, :result => 1)
|
58
|
+
response = query.call(query_name, event_collection, extra_query_hash)
|
59
|
+
response.should == api_response["result"]
|
60
|
+
expect_keen_get(expected_url, "sync")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should call the API w/ proper headers and return the processed json response" do
|
64
|
+
test_query
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should encode filters properly" do
|
68
|
+
filters = [{
|
69
|
+
:property_name => "animal",
|
70
|
+
:operator => "eq",
|
71
|
+
:property_value => "dogs"
|
72
|
+
}]
|
73
|
+
filter_str = MultiJson.encode(filters)
|
74
|
+
test_query("&filters=#{filter_str}", :filters => filters)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should encode absolute timeframes properly" do
|
78
|
+
timeframe = {
|
79
|
+
:start => "2012-08-13T19:00Z",
|
80
|
+
:end => "2012-08-13T19:00Z",
|
81
|
+
}
|
82
|
+
timeframe_str = MultiJson.encode(timeframe)
|
83
|
+
test_query("&timeframe=#{timeframe_str}", :timeframe => timeframe)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should encode steps properly" do
|
87
|
+
steps = [{
|
88
|
+
:event_collection => "signups",
|
89
|
+
:actor_property => "user.id"
|
90
|
+
}]
|
91
|
+
steps_str = MultiJson.encode(steps)
|
92
|
+
test_query("&steps=#{steps_str}", :steps => steps)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should not encode relative timeframes" do
|
96
|
+
timeframe = "last_10_days"
|
97
|
+
test_query("&timeframe=#{timeframe}", :timeframe => timeframe)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should raise a failed responses" do
|
101
|
+
query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
|
102
|
+
url = query_url(query_name, query_params)
|
103
|
+
|
104
|
+
stub_keen_get(url, 401, :error => {})
|
105
|
+
expect {
|
106
|
+
query.call(query_name, event_collection, {})
|
107
|
+
}.to raise_error(Keen::AuthenticationError)
|
108
|
+
expect_keen_get(url, "sync")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#count" do
|
114
|
+
it "should not require params" do
|
115
|
+
query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
|
116
|
+
url = query_url("count", query_params)
|
117
|
+
stub_keen_get(url, 200, :result => 10)
|
118
|
+
client.count(event_collection).should == 10
|
119
|
+
expect_keen_get(url, "sync")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#extraction" do
|
124
|
+
it "should not require params" do
|
125
|
+
query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
|
126
|
+
url = query_url("extraction", query_params)
|
127
|
+
stub_keen_get(url, 200, :result => { "a" => 1 } )
|
128
|
+
client.extraction(event_collection).should == { "a" => 1 }
|
129
|
+
expect_keen_get(url, "sync")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|