keen 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -0
- data/lib/keen.rb +4 -1
- data/lib/keen/access_keys.rb +93 -0
- data/lib/keen/client.rb +5 -2
- data/lib/keen/saved_queries.rb +83 -0
- data/lib/keen/scoped_key.rb +3 -0
- data/lib/keen/version.rb +1 -1
- data/spec/integration/access_keys_spec.rb +70 -0
- data/spec/integration/api_spec.rb +57 -61
- data/spec/keen/access_keys_spec.rb +113 -0
- data/spec/keen/saved_query_spec.rb +45 -45
- metadata +8 -3
- data/lib/keen/client/saved_queries.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 065f80ef6be231a709d26afe30ae76a8a1138812
|
4
|
+
data.tar.gz: d6a07538cff945becfa38fc12015f1e0244d9032
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40e792c1cd92e85b901b365ad3f27a146e964094cb25475a252b36f903760eb319055d620eb2ceee371ccb7fbb3bc7bfddb306c0b871b07b50d96e741f2f4a5e
|
7
|
+
data.tar.gz: 5b65635514c0be2d55107f3d1aa3a532a2d502f3b9599817daa691e9adcfb6f1e3ff8505e9ad74b6ee2d296fef8f099e4a67080e7717a0a0dbb577c602d44201
|
data/README.md
CHANGED
@@ -355,6 +355,8 @@ This is helpful for tracking email clickthroughs. See the [redirect documentatio
|
|
355
355
|
|
356
356
|
#### Generating scoped keys
|
357
357
|
|
358
|
+
Note, Scoped Keys are now *deprecated* in favor of [access keys](https://keen.io/docs/api/#access-keys?s=gh-gem).
|
359
|
+
|
358
360
|
A [scoped key](https://keen.io/docs/security/#scoped-key?s=gh-gem) is a string, generated with your API Key, that represents some encrypted authentication and query options.
|
359
361
|
Use them to control what data queries have access to.
|
360
362
|
|
@@ -369,6 +371,34 @@ scoped_key = Keen::ScopedKey.new("my-api-key", { "filters" => [{
|
|
369
371
|
|
370
372
|
You can use the scoped key created in Ruby for API requests from any client. Scoped keys are commonly used in JavaScript, where credentials are visible and need to be protected.
|
371
373
|
|
374
|
+
#### Access Keys
|
375
|
+
|
376
|
+
You can use access keys to restrict the functionality of a key you use with the Keen API. Access keys can also enrich events that you send.
|
377
|
+
|
378
|
+
[Read up](https://keen.io/docs/api/#access-keys?s=gh-gem) on the full key body options.
|
379
|
+
|
380
|
+
Create a key that automatically adds information to each event published with that key:
|
381
|
+
|
382
|
+
``` ruby
|
383
|
+
key_body = {
|
384
|
+
"name" => "autofill foo",
|
385
|
+
"is_active" => true,
|
386
|
+
"permitted" => ["writes"],
|
387
|
+
"options" => {
|
388
|
+
"writes" => {
|
389
|
+
"autofill": {
|
390
|
+
"foo": "bar"
|
391
|
+
}
|
392
|
+
}
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
new_key = client.access_keys.create(key_body)
|
397
|
+
autofill_write_key = new_key["key"]
|
398
|
+
```
|
399
|
+
|
400
|
+
You can `revoke` and `unrevoke` keys to disable or enable access. `all` will return all current keys for the project, while `get("key-value-here")` will return info for a single key. You can also `update` and `delete` keys.
|
401
|
+
|
372
402
|
### Additional options
|
373
403
|
|
374
404
|
##### HTTP Read Timeout
|
@@ -417,6 +447,11 @@ If you want some bot protection, check out the [Voight-Kampff](https://github.co
|
|
417
447
|
|
418
448
|
### Changelog
|
419
449
|
|
450
|
+
##### 1.1.0
|
451
|
+
+ Add support for access keys
|
452
|
+
+ Move saved queries into the Keen namespace
|
453
|
+
+ Deprecate scoped keys in favor of access keys
|
454
|
+
|
420
455
|
##### 1.0.0
|
421
456
|
+ Remove support for ruby 1.9.3
|
422
457
|
+ Update a few dependencies
|
data/lib/keen.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'forwardable'
|
3
3
|
|
4
|
+
require 'keen/access_keys'
|
4
5
|
require 'keen/client'
|
6
|
+
require 'keen/saved_queries'
|
5
7
|
require 'keen/scoped_key'
|
6
8
|
|
7
9
|
module Keen
|
@@ -52,7 +54,8 @@ module Keen
|
|
52
54
|
:project_info,
|
53
55
|
:query_url,
|
54
56
|
:query,
|
55
|
-
:saved_queries
|
57
|
+
:saved_queries,
|
58
|
+
:access_keys
|
56
59
|
|
57
60
|
attr_writer :logger
|
58
61
|
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Keen
|
4
|
+
class AccessKeys
|
5
|
+
def initialize(client)
|
6
|
+
@client = client
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(key)
|
10
|
+
client.ensure_master_key!
|
11
|
+
path = "/#{key}"
|
12
|
+
|
13
|
+
response = access_keys_get(client.master_key, path)
|
14
|
+
client.process_response(response.code.to_i, response.body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def all()
|
18
|
+
client.ensure_master_key!
|
19
|
+
|
20
|
+
response = access_keys_get(client.master_key)
|
21
|
+
client.process_response(response.code.to_i, response.body)
|
22
|
+
end
|
23
|
+
|
24
|
+
# For information on the format of the key_body, see
|
25
|
+
# https://keen.io/docs/api/#access-keys
|
26
|
+
def create(key_body)
|
27
|
+
client.ensure_master_key!
|
28
|
+
|
29
|
+
path = ""
|
30
|
+
response = access_keys_post(client.master_key, path, key_body)
|
31
|
+
client.process_response(response.code.to_i, response.body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def update(key, key_body)
|
35
|
+
client.ensure_master_key!
|
36
|
+
|
37
|
+
path = "/#{key}"
|
38
|
+
response = access_keys_post(client.master_key, path, key_body)
|
39
|
+
client.process_response(response.code.to_i, response.body)
|
40
|
+
end
|
41
|
+
|
42
|
+
def revoke(key)
|
43
|
+
client.ensure_master_key!
|
44
|
+
|
45
|
+
path = "/#{key}/revoke"
|
46
|
+
response = access_keys_post(client.master_key, path)
|
47
|
+
client.process_response(response.code.to_i, response.body)
|
48
|
+
end
|
49
|
+
|
50
|
+
def unrevoke(key)
|
51
|
+
client.ensure_master_key!
|
52
|
+
|
53
|
+
path = "/#{key}/unrevoke"
|
54
|
+
response = access_keys_post(client.master_key, path)
|
55
|
+
client.process_response(response.code.to_i, response.body)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(key)
|
59
|
+
client.ensure_master_key!
|
60
|
+
|
61
|
+
response = Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).delete(
|
62
|
+
path: access_keys_base_url + "/#{key}",
|
63
|
+
headers: client.api_headers(client.master_key, "sync")
|
64
|
+
)
|
65
|
+
|
66
|
+
client.process_response(response.code.to_i, response.body)
|
67
|
+
end
|
68
|
+
|
69
|
+
def access_keys_base_url
|
70
|
+
client.ensure_project_id!
|
71
|
+
"/#{client.api_version}/projects/#{client.project_id}/keys"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :client
|
77
|
+
|
78
|
+
def access_keys_get(api_key, path = "")
|
79
|
+
Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).get(
|
80
|
+
path: access_keys_base_url + path,
|
81
|
+
headers: client.api_headers(api_key, "sync")
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def access_keys_post(api_key, path = "", body = "")
|
86
|
+
Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).post(
|
87
|
+
path: access_keys_base_url + path,
|
88
|
+
headers: client.api_headers(api_key, "sync"),
|
89
|
+
body: MultiJson.dump(body)
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/keen/client.rb
CHANGED
@@ -3,7 +3,6 @@ require 'keen/version'
|
|
3
3
|
require 'keen/client/publishing_methods'
|
4
4
|
require 'keen/client/querying_methods'
|
5
5
|
require 'keen/client/maintenance_methods'
|
6
|
-
require 'keen/client/saved_queries'
|
7
6
|
require 'keen/version'
|
8
7
|
require 'openssl'
|
9
8
|
require 'multi_json'
|
@@ -62,7 +61,9 @@ module Keen
|
|
62
61
|
@saved_queries ||= SavedQueries.new(self)
|
63
62
|
end
|
64
63
|
|
65
|
-
|
64
|
+
def access_keys
|
65
|
+
@access_keys ||= AccessKeys.new(self)
|
66
|
+
end
|
66
67
|
|
67
68
|
def process_response(status_code, response_body)
|
68
69
|
case status_code.to_i
|
@@ -102,6 +103,8 @@ module Keen
|
|
102
103
|
raise ConfigurationError, "Read Key must be set for this operation" unless self.read_key
|
103
104
|
end
|
104
105
|
|
106
|
+
private
|
107
|
+
|
105
108
|
def api_event_collection_resource_path(event_collection)
|
106
109
|
encoded_collection_name = Addressable::URI.encode_component(event_collection.to_s)
|
107
110
|
encoded_collection_name.gsub! '/', '%2F'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'keen/version'
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Keen
|
5
|
+
class SavedQueries
|
6
|
+
def initialize(client)
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def all
|
11
|
+
client.ensure_master_key!
|
12
|
+
|
13
|
+
response = saved_query_response(client.master_key)
|
14
|
+
client.process_response(response.code.to_i, response.body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(saved_query_name, results = false)
|
18
|
+
saved_query_path = "/#{saved_query_name}"
|
19
|
+
if results
|
20
|
+
client.ensure_read_key!
|
21
|
+
saved_query_path += "/result"
|
22
|
+
# The results path should use the READ KEY
|
23
|
+
api_key = client.read_key
|
24
|
+
else
|
25
|
+
client.ensure_master_key!
|
26
|
+
api_key = client.master_key
|
27
|
+
end
|
28
|
+
|
29
|
+
response = saved_query_response(api_key, saved_query_path)
|
30
|
+
client.process_response(response.code.to_i, response.body)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(saved_query_name, saved_query_body)
|
34
|
+
client.ensure_master_key!
|
35
|
+
|
36
|
+
response = Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).put(
|
37
|
+
path: "#{saved_query_base_url}/#{saved_query_name}",
|
38
|
+
headers: api_headers(client.master_key, "sync"),
|
39
|
+
body: saved_query_body
|
40
|
+
)
|
41
|
+
client.process_response(response.code.to_i, response.body)
|
42
|
+
end
|
43
|
+
alias_method :update, :create
|
44
|
+
|
45
|
+
def delete(saved_query_name)
|
46
|
+
client.ensure_master_key!
|
47
|
+
|
48
|
+
response = Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).delete(
|
49
|
+
path: "#{saved_query_base_url}/#{saved_query_name}",
|
50
|
+
headers: api_headers(client.master_key, "sync")
|
51
|
+
)
|
52
|
+
client.process_response(response.code.to_i, response.body)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :client
|
58
|
+
|
59
|
+
def saved_query_response(api_key, path = "")
|
60
|
+
Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).get(
|
61
|
+
path: saved_query_base_url + path,
|
62
|
+
headers: api_headers(api_key, "sync")
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def saved_query_base_url
|
67
|
+
client.ensure_project_id!
|
68
|
+
"/#{client.api_version}/projects/#{client.project_id}/queries/saved"
|
69
|
+
end
|
70
|
+
|
71
|
+
def api_headers(authorization, sync_type)
|
72
|
+
user_agent = "keen-gem, v#{Keen::VERSION}, #{sync_type}"
|
73
|
+
user_agent += ", #{RUBY_VERSION}, #{RUBY_PLATFORM}, #{RUBY_PATCHLEVEL}"
|
74
|
+
if defined?(RUBY_ENGINE)
|
75
|
+
user_agent += ", #{RUBY_ENGINE}"
|
76
|
+
end
|
77
|
+
{ "Content-Type" => "application/json",
|
78
|
+
"User-Agent" => user_agent,
|
79
|
+
"Authorization" => authorization,
|
80
|
+
"Keen-Sdk" => "ruby-#{Keen::VERSION}" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/keen/scoped_key.rb
CHANGED
@@ -3,6 +3,7 @@ require 'keen/aes_helper'
|
|
3
3
|
require 'keen/aes_helper_old'
|
4
4
|
|
5
5
|
module Keen
|
6
|
+
# <b>DEPRECATED:</b> Please use <tt>access keys</tt> instead.
|
6
7
|
class ScopedKey
|
7
8
|
|
8
9
|
attr_accessor :api_key
|
@@ -26,6 +27,8 @@ module Keen
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def encrypt!(iv = nil)
|
30
|
+
warn "[DEPRECATION] Scoped keys are deprecated. Please use `access_keys` instead."
|
31
|
+
|
29
32
|
json_str = MultiJson.dump(self.data)
|
30
33
|
if self.api_key.length == 64
|
31
34
|
Keen::AESHelper.aes256_encrypt(self.api_key, json_str, iv)
|
data/lib/keen/version.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Access Keys" do
|
4
|
+
let(:project_id) { ENV["KEEN_PROJECT_ID"] }
|
5
|
+
let(:master_key) { ENV["KEEN_MASTER_KEY"] }
|
6
|
+
let(:client) { Keen::Client.new(project_id: project_id, master_key: master_key) }
|
7
|
+
|
8
|
+
describe "#all" do
|
9
|
+
it "gets all access keys" do
|
10
|
+
expect(client.access_keys.all).to be_instance_of(Array)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#create" do
|
15
|
+
it "creates a key" do
|
16
|
+
key_body = {
|
17
|
+
"name" => "integration test key",
|
18
|
+
"is_active" => true,
|
19
|
+
"permitted" => ["queries"],
|
20
|
+
"options" => {}
|
21
|
+
}
|
22
|
+
|
23
|
+
create_result = client.access_keys.create(key_body)
|
24
|
+
expect(create_result["name"]).to eq(key_body["name"])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#get" do
|
29
|
+
it "gets a single access key" do
|
30
|
+
all_keys = client.access_keys.all
|
31
|
+
|
32
|
+
access_key = client.access_keys.get(all_keys.first["key"])
|
33
|
+
|
34
|
+
expect(access_key["name"]).to eq(all_keys.first["name"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#revoke" do
|
39
|
+
it "sets the is_active to false" do
|
40
|
+
all_keys = client.access_keys.all
|
41
|
+
key = all_keys.first["key"]
|
42
|
+
|
43
|
+
client.access_keys.revoke(key)
|
44
|
+
new_key = client.access_keys.get(key)
|
45
|
+
expect(new_key["is_active"]).to be_falsey
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#unrevoke" do
|
50
|
+
it "sets the is_active to true" do
|
51
|
+
all_keys = client.access_keys.all
|
52
|
+
key = all_keys.first["key"]
|
53
|
+
|
54
|
+
client.access_keys.unrevoke(key)
|
55
|
+
new_key = client.access_keys.get(key)
|
56
|
+
expect(new_key["is_active"]).to be_truthy
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#delete" do
|
61
|
+
it "deletes a key" do
|
62
|
+
all_keys = client.access_keys.all
|
63
|
+
key = all_keys.first["key"]
|
64
|
+
|
65
|
+
client.access_keys.delete(key)
|
66
|
+
all_keys = client.access_keys.all
|
67
|
+
expect(all_keys).to eq([])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -7,7 +7,7 @@ describe "Keen IO API" do
|
|
7
7
|
def wait_for_count(event_collection, count)
|
8
8
|
attempts = 0
|
9
9
|
while attempts < 30
|
10
|
-
break if Keen.count(event_collection) == count
|
10
|
+
break if Keen.count(event_collection, {:timeframe => "this_2_hours"}) == count
|
11
11
|
attempts += 1
|
12
12
|
sleep(1)
|
13
13
|
end
|
@@ -20,7 +20,7 @@ describe "Keen IO API" do
|
|
20
20
|
|
21
21
|
describe "success" do
|
22
22
|
it "should return a created status for a valid post" do
|
23
|
-
Keen.publish(collection, event_properties).
|
23
|
+
expect(Keen.publish(collection, event_properties)).to eq(api_success)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -33,7 +33,7 @@ describe "Keen IO API" do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should succeed if a non-url-safe event collection is specified" do
|
36
|
-
Keen.publish("infinite possibilities", event_properties).
|
36
|
+
expect(Keen.publish("infinite possibilities", event_properties)).to eq(api_success)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -45,7 +45,7 @@ describe "Keen IO API" do
|
|
45
45
|
EM.run {
|
46
46
|
Keen.publish_async(collection, event_properties).callback { |response|
|
47
47
|
begin
|
48
|
-
response.
|
48
|
+
expect(response).to eq(api_success)
|
49
49
|
ensure
|
50
50
|
EM.stop
|
51
51
|
end
|
@@ -60,7 +60,7 @@ describe "Keen IO API" do
|
|
60
60
|
EM.run {
|
61
61
|
Keen.publish_async("foo bar", event_properties).callback { |response|
|
62
62
|
begin
|
63
|
-
response.
|
63
|
+
expect(response).to eq(api_success)
|
64
64
|
ensure
|
65
65
|
EM.stop
|
66
66
|
end
|
@@ -72,7 +72,7 @@ describe "Keen IO API" do
|
|
72
72
|
|
73
73
|
describe "batch" do
|
74
74
|
it "should publish a batch of events" do
|
75
|
-
Keen.publish_batch(
|
75
|
+
expect(Keen.publish_batch(
|
76
76
|
:batch_signups => [
|
77
77
|
{ :name => "bob" },
|
78
78
|
{ :name => "ted" }
|
@@ -81,7 +81,7 @@ describe "Keen IO API" do
|
|
81
81
|
{ :price => 30 },
|
82
82
|
{ :price => 40 }
|
83
83
|
]
|
84
|
-
).
|
84
|
+
)).to eq({
|
85
85
|
"batch_purchases" => [
|
86
86
|
{ "success" => true },
|
87
87
|
{ "success" => true }
|
@@ -89,7 +89,7 @@ describe "Keen IO API" do
|
|
89
89
|
"batch_signups" => [
|
90
90
|
{ "success" => true },
|
91
91
|
{ "success"=>true }
|
92
|
-
]}
|
92
|
+
]})
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -110,7 +110,7 @@ describe "Keen IO API" do
|
|
110
110
|
{ :price => 40 }
|
111
111
|
]).callback { |response|
|
112
112
|
begin
|
113
|
-
response.
|
113
|
+
expect(response).to eq(api_success)
|
114
114
|
ensure
|
115
115
|
EM.stop
|
116
116
|
end
|
@@ -151,111 +151,115 @@ describe "Keen IO API" do
|
|
151
151
|
end
|
152
152
|
|
153
153
|
it "should return a valid count_unique" do
|
154
|
-
Keen.count_unique(event_collection, :target_property => "price").
|
154
|
+
expect(Keen.count_unique(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(2)
|
155
155
|
end
|
156
156
|
|
157
157
|
it "should return a valid count with group_by" do
|
158
|
-
response = Keen.average(event_collection, :group_by => "username", :target_property => "price")
|
158
|
+
response = Keen.average(event_collection, :timeframe => "this_2_hours", :group_by => "username", :target_property => "price")
|
159
159
|
bobs_response = response.select { |result| result["username"] == "bob" }.first
|
160
|
-
bobs_response["result"].
|
160
|
+
expect(bobs_response["result"]).to eq(10)
|
161
161
|
teds_response = response.select { |result| result["username"] == "ted" }.first
|
162
|
-
teds_response["result"].
|
162
|
+
expect(teds_response["result"]).to eq(20)
|
163
163
|
end
|
164
164
|
|
165
165
|
it "should return a valid count with multi-group_by" do
|
166
|
-
response = Keen.average(event_collection, :group_by => ["username", "price"], :target_property => "price")
|
166
|
+
response = Keen.average(event_collection, :timeframe => "this_2_hours", :group_by => ["username", "price"], :target_property => "price")
|
167
167
|
bobs_response = response.select { |result| result["username"] == "bob" }.first
|
168
|
-
bobs_response["result"].
|
169
|
-
bobs_response["price"].
|
168
|
+
expect(bobs_response["result"]).to eq(10)
|
169
|
+
expect(bobs_response["price"]).to eq(10)
|
170
170
|
teds_response = response.select { |result| result["username"] == "ted" }.first
|
171
|
-
teds_response["result"].
|
172
|
-
teds_response["price"].
|
171
|
+
expect(teds_response["result"]).to eq(20)
|
172
|
+
expect(teds_response["price"]).to eq(20)
|
173
173
|
end
|
174
174
|
|
175
175
|
it "should return a valid sum" do
|
176
|
-
Keen.sum(event_collection, :target_property => "price").
|
176
|
+
expect(Keen.sum(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(30)
|
177
177
|
end
|
178
178
|
|
179
179
|
it "should return a valid minimum" do
|
180
|
-
Keen.minimum(event_collection, :target_property => "price").
|
180
|
+
expect(Keen.minimum(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(10)
|
181
181
|
end
|
182
182
|
|
183
183
|
it "should return a valid maximum" do
|
184
|
-
Keen.maximum(event_collection, :target_property => "price").
|
184
|
+
expect(Keen.maximum(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(20)
|
185
185
|
end
|
186
186
|
|
187
187
|
it "should return a valid average" do
|
188
|
-
Keen.average(event_collection, :target_property => "price").
|
188
|
+
expect(Keen.average(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(15)
|
189
189
|
end
|
190
190
|
|
191
191
|
it "should return a valid median" do
|
192
|
-
Keen.median(event_collection, :target_property => "price").
|
192
|
+
expect(Keen.median(event_collection, :timeframe => "this_2_hours", :target_property => "price")).to eq(10)
|
193
193
|
end
|
194
194
|
|
195
195
|
it "should return a valid percentile" do
|
196
|
-
Keen.percentile(event_collection, :target_property => "price", :percentile => 50).
|
197
|
-
Keen.percentile(event_collection, :target_property => "price", :percentile => 100).
|
196
|
+
expect(Keen.percentile(event_collection, :timeframe => "this_2_hours", :target_property => "price", :percentile => 50)).to eq(10)
|
197
|
+
expect(Keen.percentile(event_collection, :timeframe => "this_2_hours", :target_property => "price", :percentile => 100)).to eq(20)
|
198
198
|
end
|
199
199
|
|
200
200
|
it "should return a valid select_unique" do
|
201
|
-
results = Keen.select_unique(event_collection, :target_property => "price")
|
202
|
-
results.sort.
|
201
|
+
results = Keen.select_unique(event_collection, :timeframe => "this_2_hours", :target_property => "price")
|
202
|
+
expect(results.sort).to eq([10, 20].sort)
|
203
203
|
end
|
204
204
|
|
205
205
|
it "should return a valid extraction" do
|
206
|
-
results = Keen.extraction(event_collection)
|
207
|
-
results.length.
|
208
|
-
results.all? { |result| result["keen"] }.
|
209
|
-
results.map { |result| result["price"] }.sort.
|
210
|
-
results.map { |result| result["username"] }.sort.
|
206
|
+
results = Keen.extraction(event_collection, :timeframe => "this_2_hours")
|
207
|
+
expect(results.length).to eq(2)
|
208
|
+
expect(results.all? { |result| result["keen"] }).to be_truthy
|
209
|
+
expect(results.map { |result| result["price"] }.sort).to eq([10, 20])
|
210
|
+
expect(results.map { |result| result["username"] }.sort).to eq(["bob", "ted"])
|
211
211
|
end
|
212
212
|
|
213
213
|
it "should return a valid extraction of one property name" do
|
214
|
-
results = Keen.extraction(event_collection, :property_names => "price")
|
215
|
-
results.length.
|
216
|
-
results.any? { |result| result["keen"] }.
|
217
|
-
results.map { |result| result["price"] }.sort.
|
218
|
-
results.map { |result| result["username"] }.sort.
|
214
|
+
results = Keen.extraction(event_collection, :timeframe => "this_2_hours", :property_names => "price")
|
215
|
+
expect(results.length).to eq(2)
|
216
|
+
expect(results.any? { |result| result["keen"] }).to be_falsey
|
217
|
+
expect(results.map { |result| result["price"] }.sort).to eq([10, 20])
|
218
|
+
expect(results.map { |result| result["username"] }.sort).to eq([nil, nil])
|
219
219
|
end
|
220
220
|
|
221
221
|
it "should return a valid extraction of more than one property name" do
|
222
|
-
results = Keen.extraction(event_collection, :property_names => ["price", "username"])
|
223
|
-
results.length.
|
224
|
-
results.any? { |result| result["keen"] }.
|
225
|
-
results.map { |result| result["price"] }.sort.
|
226
|
-
results.map { |result| result["username"] }.sort.
|
222
|
+
results = Keen.extraction(event_collection, :timeframe => "this_2_hours", :property_names => ["price", "username"])
|
223
|
+
expect(results.length).to eq(2)
|
224
|
+
expect(results.any? { |result| result["keen"] }).to be_falsey
|
225
|
+
expect(results.map { |result| result["price"] }.sort).to eq([10, 20])
|
226
|
+
expect(results.map { |result| result["username"] }.sort).to eq(["bob", "ted"])
|
227
227
|
end
|
228
228
|
|
229
229
|
it "should return a valid funnel" do
|
230
230
|
steps = [{
|
231
231
|
:event_collection => event_collection,
|
232
|
-
:actor_property => "username"
|
232
|
+
:actor_property => "username",
|
233
|
+
:timeframe => "this_2_hours"
|
233
234
|
}, {
|
234
235
|
:event_collection => @returns_event_collection,
|
235
|
-
:actor_property => "username"
|
236
|
+
:actor_property => "username",
|
237
|
+
:timeframe => "this_2_hours"
|
236
238
|
}]
|
237
239
|
results = Keen.funnel(:steps => steps)
|
238
|
-
results.
|
240
|
+
expect(results).to eq([2, 1])
|
239
241
|
end
|
240
242
|
|
241
243
|
it "should return all keys of valid funnel if full result option is passed" do
|
242
244
|
steps = [{
|
245
|
+
:timeframe => "this_2_hours",
|
243
246
|
:event_collection => event_collection,
|
244
247
|
:actor_property => "username"
|
245
248
|
}, {
|
249
|
+
:timeframe => "this_2_hours",
|
246
250
|
:event_collection => @returns_event_collection,
|
247
251
|
:actor_property => "username"
|
248
252
|
}]
|
249
253
|
results = Keen.funnel({ :steps => steps }, { :response => :all_keys })
|
250
|
-
results["result"].
|
254
|
+
expect(results["result"]).to eq([2, 1])
|
251
255
|
end
|
252
256
|
|
253
257
|
it "should apply filters" do
|
254
|
-
Keen.count(event_collection, :filters => [{
|
258
|
+
expect(Keen.count(event_collection, :timeframe => "this_2_hours", :filters => [{
|
255
259
|
:property_name => "username",
|
256
260
|
:operator => "eq",
|
257
261
|
:property_value => "ted"
|
258
|
-
}]).
|
262
|
+
}])).to eq(1)
|
259
263
|
end
|
260
264
|
end
|
261
265
|
|
@@ -273,9 +277,9 @@ describe "Keen IO API" do
|
|
273
277
|
{ :property_name => "delete", :operator => "eq", :property_value => "me" }
|
274
278
|
])
|
275
279
|
wait_for_count(event_collection, 1)
|
276
|
-
results = Keen.extraction(event_collection)
|
277
|
-
results.length.
|
278
|
-
results.first["delete"].
|
280
|
+
results = Keen.extraction(event_collection, :timeframe => "this_2_hours")
|
281
|
+
expect(results.length).to eq(1)
|
282
|
+
expect(results.first["delete"]).to eq("you")
|
279
283
|
end
|
280
284
|
end
|
281
285
|
|
@@ -286,21 +290,13 @@ describe "Keen IO API" do
|
|
286
290
|
# requires a project with at least 1 collection
|
287
291
|
it "should return the project's collections as JSON" do
|
288
292
|
first_collection = Keen.event_collections.first
|
289
|
-
first_collection["properties"]["keen.timestamp"].
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
describe "event_collection" do
|
294
|
-
# requires a project with at least 1 collection
|
295
|
-
it "should return the project's named collection as JSON" do
|
296
|
-
first_collection = Keen.event_collection(:event_collection)
|
297
|
-
first_collection["properties"]["keen.timestamp"].should == "datetime"
|
293
|
+
expect(first_collection["properties"]["keen.timestamp"]).to eq("datetime")
|
298
294
|
end
|
299
295
|
end
|
300
296
|
|
301
297
|
describe "project_info" do
|
302
298
|
it "should return the project info as JSON" do
|
303
|
-
Keen.project_info["url"].
|
299
|
+
expect(Keen.project_info["url"]).to include(project_id)
|
304
300
|
|
305
301
|
end
|
306
302
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Keen do
|
4
|
+
let(:client) do
|
5
|
+
Keen::Client.new(
|
6
|
+
project_id: "12341234",
|
7
|
+
master_key: "abcdef",
|
8
|
+
api_url: "https://notreal.keen.io"
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#access_keys" do
|
13
|
+
describe "#all" do
|
14
|
+
|
15
|
+
it "returns all access keys" do
|
16
|
+
all_access_keys = [
|
17
|
+
key_object()
|
18
|
+
]
|
19
|
+
|
20
|
+
stub_keen_get(access_keys_endpoint, 200, all_access_keys)
|
21
|
+
|
22
|
+
all_access_keys_response = client.access_keys.all
|
23
|
+
|
24
|
+
expect(all_access_keys_response).to eq(all_access_keys)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#get" do
|
29
|
+
it "returns a specific access key given a key" do
|
30
|
+
key = key_object()
|
31
|
+
|
32
|
+
stub_keen_get(
|
33
|
+
access_keys_endpoint + "/#{key["key"]}",
|
34
|
+
200,
|
35
|
+
key
|
36
|
+
)
|
37
|
+
|
38
|
+
access_key_response = client.access_keys.get(key["key"])
|
39
|
+
|
40
|
+
expect(access_key_response).to eq(key)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#create" do
|
45
|
+
it "returns the created saved query when creation is successful" do
|
46
|
+
key = key_object()
|
47
|
+
|
48
|
+
stub_keen_post(
|
49
|
+
access_keys_endpoint, 201, key
|
50
|
+
)
|
51
|
+
|
52
|
+
access_keys_response = client.access_keys.create(key)
|
53
|
+
|
54
|
+
expect(access_keys_response).to eq(key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#update" do
|
59
|
+
it "returns the updated access key when update is successful" do
|
60
|
+
key = key_object()
|
61
|
+
|
62
|
+
stub_keen_post(
|
63
|
+
access_keys_endpoint + "/#{key["key"]}", 200, key
|
64
|
+
)
|
65
|
+
|
66
|
+
access_keys_response = client.access_keys.update(key["key"], key)
|
67
|
+
|
68
|
+
expect(access_keys_response).to eq(key)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#delete" do
|
73
|
+
it "returns true with deletion is successful" do
|
74
|
+
key = "asdf1234"
|
75
|
+
|
76
|
+
stub_keen_delete(
|
77
|
+
access_keys_endpoint + "/#{key}", 204
|
78
|
+
)
|
79
|
+
|
80
|
+
access_keys_response = client.access_keys.delete(key)
|
81
|
+
|
82
|
+
expect(access_keys_response).to eq(true)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def access_keys_endpoint
|
88
|
+
client.api_url + "/#{client.api_version}/projects/#{client.project_id}/keys"
|
89
|
+
end
|
90
|
+
|
91
|
+
def key_object(name = "Test Access Key")
|
92
|
+
{
|
93
|
+
"key" => "SDKFJSDKFJSDKFJSDKFJDSK",
|
94
|
+
"name" => name,
|
95
|
+
"is_active" => true,
|
96
|
+
"permitted" => ["queries", "cached_queries"],
|
97
|
+
"options" => {
|
98
|
+
"queries" => {
|
99
|
+
"filters" => [
|
100
|
+
{
|
101
|
+
"property_name" => "customer.id",
|
102
|
+
"operator" => "eq",
|
103
|
+
"property_value" => "asdf12345z"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
},
|
107
|
+
"cached_queries" => {
|
108
|
+
"allowed" => ["my_cached_query"]
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
@@ -14,18 +14,18 @@ describe Keen do
|
|
14
14
|
|
15
15
|
it "returns all saved queries" do
|
16
16
|
all_saved_queries = [ {
|
17
|
-
refresh_rate
|
18
|
-
last_modified_date
|
19
|
-
query_name
|
20
|
-
query
|
21
|
-
filters
|
22
|
-
analysis_type
|
23
|
-
timezone
|
24
|
-
timeframe
|
25
|
-
event_collection
|
17
|
+
"refresh_rate" => 0,
|
18
|
+
"last_modified_date" => "2015-10-19T20:14:29.797000+00:00",
|
19
|
+
"query_name" => "Analysis-API-Calls-this-1-day",
|
20
|
+
"query" => {
|
21
|
+
"filters" => [],
|
22
|
+
"analysis_type" => "count",
|
23
|
+
"timezone" => "UTC",
|
24
|
+
"timeframe" => "this_1_days",
|
25
|
+
"event_collection" => "analysis_api_call"
|
26
26
|
},
|
27
|
-
metadata
|
28
|
-
visualization
|
27
|
+
"metadata" => {
|
28
|
+
"visualization" => { "chart_type" => "metric"}
|
29
29
|
}
|
30
30
|
} ]
|
31
31
|
stub_keen_get(saved_query_endpoint, 200, all_saved_queries)
|
@@ -39,22 +39,22 @@ describe Keen do
|
|
39
39
|
describe "#get" do
|
40
40
|
it "returns a specific saved query given a query id" do
|
41
41
|
saved_query = {
|
42
|
-
refresh_rate
|
43
|
-
last_modified_date
|
44
|
-
query_name
|
45
|
-
query
|
46
|
-
filters
|
47
|
-
analysis_type
|
48
|
-
timezone
|
49
|
-
timeframe
|
50
|
-
event_collection
|
42
|
+
"refresh_rate" => 0,
|
43
|
+
"last_modified_date" => "2015-10-19T20:14:29.797000+00:00",
|
44
|
+
"query_name" => "Analysis-API-Calls-this-1-day",
|
45
|
+
"query" => {
|
46
|
+
"filters" => [],
|
47
|
+
"analysis_type" => "count",
|
48
|
+
"timezone" => "UTC",
|
49
|
+
"timeframe" => "this_1_days",
|
50
|
+
"event_collection" => "analysis_api_call"
|
51
51
|
},
|
52
|
-
metadata
|
53
|
-
visualization
|
52
|
+
"metadata" => {
|
53
|
+
"visualization" => { "chart_type" => "metric"}
|
54
54
|
}
|
55
55
|
}
|
56
56
|
stub_keen_get(
|
57
|
-
saved_query_endpoint + "/#{saved_query[
|
57
|
+
saved_query_endpoint + "/#{saved_query["query_name"]}",
|
58
58
|
200,
|
59
59
|
saved_query
|
60
60
|
)
|
@@ -84,18 +84,18 @@ describe Keen do
|
|
84
84
|
describe "#create" do
|
85
85
|
it "returns the created saved query when creation is successful" do
|
86
86
|
saved_query = {
|
87
|
-
refresh_rate
|
88
|
-
last_modified_date
|
89
|
-
query_name
|
90
|
-
query
|
91
|
-
filters
|
92
|
-
analysis_type
|
93
|
-
timezone
|
94
|
-
timeframe
|
95
|
-
event_collection
|
87
|
+
"refresh_rate" => 0,
|
88
|
+
"last_modified_date" => "2015-10-19T20:14:29.797000+00:00",
|
89
|
+
"query_name" => "new-query",
|
90
|
+
"query" => {
|
91
|
+
"filters" => [],
|
92
|
+
"analysis_type" => "count",
|
93
|
+
"timezone" => "UTC",
|
94
|
+
"timeframe" => "this_1_days",
|
95
|
+
"event_collection" => "analysis_api_call"
|
96
96
|
},
|
97
|
-
metadata
|
98
|
-
visualization
|
97
|
+
"metadata" => {
|
98
|
+
"visualization" => { "chart_type" => "metric"}
|
99
99
|
}
|
100
100
|
}
|
101
101
|
stub_keen_put(
|
@@ -121,18 +121,18 @@ describe Keen do
|
|
121
121
|
describe "#update" do
|
122
122
|
it "returns the created saved query when update is successful" do
|
123
123
|
saved_query = {
|
124
|
-
refresh_rate
|
125
|
-
last_modified_date
|
126
|
-
query_name
|
127
|
-
query
|
128
|
-
filters
|
129
|
-
analysis_type
|
130
|
-
timezone
|
131
|
-
timeframe
|
132
|
-
event_collection
|
124
|
+
"refresh_rate" => 0,
|
125
|
+
"last_modified_date" => "2015-10-19T20:14:29.797000+00:00",
|
126
|
+
"query_name" => "new-query",
|
127
|
+
"query" => {
|
128
|
+
"filters" => [],
|
129
|
+
"analysis_type" => "count",
|
130
|
+
"timezone" => "UTC",
|
131
|
+
"timeframe" => "this_1_days",
|
132
|
+
"event_collection" => "analysis_api_call"
|
133
133
|
},
|
134
|
-
metadata
|
135
|
-
visualization
|
134
|
+
"metadata" => {
|
135
|
+
"visualization" => { "chart_type" => "metric"}
|
136
136
|
}
|
137
137
|
}
|
138
138
|
stub_keen_put(
|
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: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kleissner
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-06-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -154,19 +154,22 @@ files:
|
|
154
154
|
- config/cacert.pem
|
155
155
|
- keen.gemspec
|
156
156
|
- lib/keen.rb
|
157
|
+
- lib/keen/access_keys.rb
|
157
158
|
- lib/keen/aes_helper.rb
|
158
159
|
- lib/keen/aes_helper_old.rb
|
159
160
|
- lib/keen/client.rb
|
160
161
|
- lib/keen/client/maintenance_methods.rb
|
161
162
|
- lib/keen/client/publishing_methods.rb
|
162
163
|
- lib/keen/client/querying_methods.rb
|
163
|
-
- lib/keen/client/saved_queries.rb
|
164
164
|
- lib/keen/http.rb
|
165
|
+
- lib/keen/saved_queries.rb
|
165
166
|
- lib/keen/scoped_key.rb
|
166
167
|
- lib/keen/version.rb
|
168
|
+
- spec/integration/access_keys_spec.rb
|
167
169
|
- spec/integration/api_spec.rb
|
168
170
|
- spec/integration/saved_query_spec.rb
|
169
171
|
- spec/integration/spec_helper.rb
|
172
|
+
- spec/keen/access_keys_spec.rb
|
170
173
|
- spec/keen/client/maintenance_methods_spec.rb
|
171
174
|
- spec/keen/client/publishing_methods_spec.rb
|
172
175
|
- spec/keen/client/querying_methods_spec.rb
|
@@ -204,9 +207,11 @@ signing_key:
|
|
204
207
|
specification_version: 4
|
205
208
|
summary: Keen IO API Client
|
206
209
|
test_files:
|
210
|
+
- spec/integration/access_keys_spec.rb
|
207
211
|
- spec/integration/api_spec.rb
|
208
212
|
- spec/integration/saved_query_spec.rb
|
209
213
|
- spec/integration/spec_helper.rb
|
214
|
+
- spec/keen/access_keys_spec.rb
|
210
215
|
- spec/keen/client/maintenance_methods_spec.rb
|
211
216
|
- spec/keen/client/publishing_methods_spec.rb
|
212
217
|
- spec/keen/client/querying_methods_spec.rb
|
@@ -1,88 +0,0 @@
|
|
1
|
-
require 'keen/version'
|
2
|
-
require "json"
|
3
|
-
|
4
|
-
class SavedQueries
|
5
|
-
def initialize(client)
|
6
|
-
@client = client
|
7
|
-
end
|
8
|
-
|
9
|
-
def all
|
10
|
-
process_response(saved_query_response(client.master_key))
|
11
|
-
end
|
12
|
-
|
13
|
-
def get(saved_query_name, results = false)
|
14
|
-
saved_query_path = "/#{saved_query_name}"
|
15
|
-
api_key = client.master_key
|
16
|
-
if results
|
17
|
-
saved_query_path += "/result"
|
18
|
-
# The results path should use the READ KEY
|
19
|
-
api_key = client.read_key
|
20
|
-
end
|
21
|
-
|
22
|
-
response = saved_query_response(api_key, saved_query_path)
|
23
|
-
response_body = JSON.parse(response.body, symbolize_names: true)
|
24
|
-
process_response(response)
|
25
|
-
end
|
26
|
-
|
27
|
-
def create(saved_query_name, saved_query_body)
|
28
|
-
response = Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).put(
|
29
|
-
path: "#{saved_query_base_url}/#{saved_query_name}",
|
30
|
-
headers: api_headers(client.master_key, "sync"),
|
31
|
-
body: saved_query_body
|
32
|
-
)
|
33
|
-
process_response(response)
|
34
|
-
end
|
35
|
-
alias_method :update, :create
|
36
|
-
|
37
|
-
def delete(saved_query_name)
|
38
|
-
response = Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).delete(
|
39
|
-
path: "#{saved_query_base_url}/#{saved_query_name}",
|
40
|
-
headers: api_headers(client.master_key, "sync")
|
41
|
-
)
|
42
|
-
process_response(response)
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
attr_reader :client
|
48
|
-
|
49
|
-
def saved_query_response(api_key, path = "")
|
50
|
-
Keen::HTTP::Sync.new(client.api_url, client.proxy_url, client.read_timeout, client.open_timeout).get(
|
51
|
-
path: saved_query_base_url + path,
|
52
|
-
headers: api_headers(api_key, "sync")
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
def saved_query_base_url
|
57
|
-
"/#{client.api_version}/projects/#{client.project_id}/queries/saved"
|
58
|
-
end
|
59
|
-
|
60
|
-
def api_headers(authorization, sync_type)
|
61
|
-
user_agent = "keen-gem, v#{Keen::VERSION}, #{sync_type}"
|
62
|
-
user_agent += ", #{RUBY_VERSION}, #{RUBY_PLATFORM}, #{RUBY_PATCHLEVEL}"
|
63
|
-
if defined?(RUBY_ENGINE)
|
64
|
-
user_agent += ", #{RUBY_ENGINE}"
|
65
|
-
end
|
66
|
-
{ "Content-Type" => "application/json",
|
67
|
-
"User-Agent" => user_agent,
|
68
|
-
"Authorization" => authorization,
|
69
|
-
"Keen-Sdk" => "ruby-#{Keen::VERSION}" }
|
70
|
-
end
|
71
|
-
|
72
|
-
def process_response(response)
|
73
|
-
case response.code.to_i
|
74
|
-
when 204
|
75
|
-
true
|
76
|
-
when 200..299
|
77
|
-
JSON.parse(response.body, symbolize_names: true)
|
78
|
-
when 400
|
79
|
-
raise Keen::BadRequestError.new(response.body)
|
80
|
-
when 401
|
81
|
-
raise Keen::AuthenticationError.new(response.body)
|
82
|
-
when 404
|
83
|
-
raise Keen::NotFoundError.new(response.body)
|
84
|
-
else
|
85
|
-
raise Keen::HttpError.new(response.body)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|