keen 0.7.8 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +8 -2
- data/Guardfile +3 -3
- data/LICENSE +1 -1
- data/README.md +62 -8
- data/keen.gemspec +18 -10
- data/lib/keen.rb +1 -0
- data/lib/keen/aes_helper.rb +43 -0
- data/lib/keen/client.rb +29 -17
- data/lib/keen/client/publishing_methods.rb +31 -24
- data/lib/keen/scoped_key.rb +35 -0
- data/lib/keen/version.rb +1 -1
- data/spec/integration/api_spec.rb +20 -2
- data/spec/keen/client/publishing_methods_spec.rb +7 -7
- data/spec/keen/client/querying_methods_spec.rb +10 -0
- data/spec/keen/scoped_key_spec.rb +39 -0
- metadata +44 -56
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3cf3ae16e58f3c518d668690727dee634e620ec
|
4
|
+
data.tar.gz: bb8e8adde18854b35095b90017dc468900e28201
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 097d04970b5ce6c0779b96655cd29aa747d1df5eeb4506dfc4763546fae08c7bd3b6f7ed48f5b4f2dcbd3a4caeb433778fda7bfa67d5871c9f9d3c2ffa4d48e2
|
7
|
+
data.tar.gz: 732360e3dd2873579493eb08f407055929cc9d27a5285ef8d592dcfd94f69999d66f4d62787a0557b96b485409bdb2cb35a5233ff270b81b73e057c0e1247cdf
|
data/.travis.yml
CHANGED
@@ -6,13 +6,19 @@ rvm:
|
|
6
6
|
- 1.9.3
|
7
7
|
- 2.0.0
|
8
8
|
- jruby-19mode
|
9
|
-
- rbx
|
9
|
+
- rbx
|
10
10
|
|
11
11
|
env:
|
12
12
|
- PATTERN=keen
|
13
|
-
|
13
|
+
#- PATTERN=integration KEEN_MASTER_KEY=f806128f31c349fda124b62d1f4cf4b2 KEEN_WRITE_KEY=f806128f31c349fda124b62d1f4cf4b2 KEEN_READ_KEY=f806128f31c349fda124b62d1f4cf4b2 KEEN_PROJECT_ID=50e5ffa6897a2c319b000000
|
14
14
|
- PATTERN=synchrony
|
15
15
|
|
16
|
+
before_install:
|
17
|
+
- gem update bundler
|
18
|
+
- bundle --version
|
19
|
+
- gem update --system 2.1.11
|
20
|
+
- gem --version
|
21
|
+
|
16
22
|
matrix:
|
17
23
|
exclude:
|
18
24
|
- rvm: 1.8.7
|
data/Guardfile
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
group :unit do
|
2
|
-
guard 'rspec', :spec_paths => "spec/keen" do
|
2
|
+
guard 'rspec', :spec_paths => ["spec/keen"] do
|
3
3
|
watch('spec/spec_helper.rb') { "spec" }
|
4
4
|
watch('spec/keen/spec_helper.rb') { "spec" }
|
5
5
|
watch(%r{^spec/keen/.+_spec\.rb$})
|
@@ -8,7 +8,7 @@ group :unit do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
group :integration do
|
11
|
-
guard 'rspec', :spec_paths => "spec/integration" do
|
11
|
+
guard 'rspec', :spec_paths => ["spec/integration"] do
|
12
12
|
watch('spec/spec_helper.rb') { "spec" }
|
13
13
|
watch('spec/integration/spec_helper.rb') { "spec" }
|
14
14
|
watch(%r{^spec/integration/.+_spec\.rb$})
|
@@ -16,7 +16,7 @@ group :integration do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
group :synchrony do
|
19
|
-
guard 'rspec', :spec_paths => "spec/synchrony" do
|
19
|
+
guard 'rspec', :spec_paths => ["spec/synchrony"] do
|
20
20
|
watch('spec/spec_helper.rb') { "spec" }
|
21
21
|
watch('spec/synchrony/spec_helper.rb') { "spec" }
|
22
22
|
watch(%r{^spec/synchrony/.+_spec\.rb$})
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -40,7 +40,7 @@ If you're using [foreman](http://ddollar.github.com/foreman/), add this to your
|
|
40
40
|
KEEN_WRITE_KEY=yyyyyyyyyyyyyyy
|
41
41
|
KEEN_READ_KEY=zzzzzzzzzzzzzzz
|
42
42
|
|
43
|
-
If not, make
|
43
|
+
If not, make a script to export the variables into your shell or put it before the command you use to start your server.
|
44
44
|
|
45
45
|
When you deploy, make sure your production environment variables are set. For example,
|
46
46
|
set [config vars](https://devcenter.heroku.com/articles/config-vars) on Heroku. (We recommend this
|
@@ -89,7 +89,7 @@ Thread.new { EventMachine.run }
|
|
89
89
|
The best place for this is in an initializer, or anywhere that runs when your app boots up.
|
90
90
|
Here's a useful blog article that explains more about this approach - [EventMachine and Passenger](http://railstips.org/blog/archives/2011/05/04/eventmachine-and-passenger/).
|
91
91
|
|
92
|
-
And here's
|
92
|
+
And here's an example repository that shows an example of [Eventmachine with Unicorn](https://github.com/dzello/em-unicorn/blob/master/unicorn.rb), specifically the Unicorn config for starting and stopping EventMachine after forking.
|
93
93
|
|
94
94
|
Now, in your code, replace `publish` with `publish_async`. Bind callbacks if you require them.
|
95
95
|
|
@@ -154,7 +154,7 @@ Keen.delete(:signups) # => true
|
|
154
154
|
|
155
155
|
# Or just delete an event corresponding to a particular user
|
156
156
|
Keen.delete(:signups, filters: [{
|
157
|
-
property_name
|
157
|
+
:property_name => 'username', ;operator => 'eq', :property_value => "Bob"
|
158
158
|
}]) # => true
|
159
159
|
```
|
160
160
|
|
@@ -207,7 +207,7 @@ If you call `publish_async` and `EM::Synchrony` is defined the method will retur
|
|
207
207
|
directly. (It does not return the deferrable on which to register callbacks.) Likewise, it will raise
|
208
208
|
exceptions 'synchronously' should they happen.
|
209
209
|
|
210
|
-
#### Beacon
|
210
|
+
#### Beacon URLs
|
211
211
|
|
212
212
|
It's possible to publish events to your Keen IO project using the HTTP GET method.
|
213
213
|
This is useful for situations like tracking email opens using [image beacons](http://en.wikipedia.org/wiki/Web_bug).
|
@@ -222,10 +222,10 @@ Keen.beacon_url("sign_ups", :recipient => "foo@foo.com")
|
|
222
222
|
# => "https://api.keen.io/3.0/projects/xxxxxx/events/email_opens?api_key=yyyyyy&data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9"
|
223
223
|
```
|
224
224
|
|
225
|
-
To track email opens, simply add an image to your email template that points to this URL.
|
225
|
+
To track email opens, simply add an image to your email template that points to this URL. For further information on how to do this, see the [image beacon documentation](https://keen.io/docs/data-collection/image-beacon/).
|
226
226
|
|
227
|
-
#### Redirect
|
228
|
-
Redirect
|
227
|
+
#### Redirect URLs
|
228
|
+
Redirect URLs are just like image beacon URLs with the addition of a `redirect` query parameter. This parameter is used
|
229
229
|
to issue a redirect to a certain URL after an event is recorded.
|
230
230
|
|
231
231
|
```
|
@@ -233,10 +233,48 @@ Keen.redirect_url("sign_ups", { :recipient => "foo@foo.com" }, "http://foo.com")
|
|
233
233
|
# => "https://api.keen.io/3.0/projects/xxxxxx/events/email_opens?api_key=yyyyyy&data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9&redirect=http://foo.com"
|
234
234
|
```
|
235
235
|
|
236
|
-
This is helpful for tracking email clickthroughs.
|
236
|
+
This is helpful for tracking email clickthroughs. See the [redirect documentation](https://keen.io/docs/data-collection/redirect/) for further information.
|
237
|
+
|
238
|
+
#### Generating scoped keys
|
239
|
+
|
240
|
+
A [scoped key](https://keen.io/docs/security/#scoped-key) is a string, generated with your API Key, that represents some encrypted authentication and query options.
|
241
|
+
Use them to control what data queries have access to.
|
242
|
+
|
243
|
+
``` ruby
|
244
|
+
# "my-api-key" should be your MASTER API key
|
245
|
+
scoped_key = Keen::ScopedKey.new("my-api-key", { "filters" => [{
|
246
|
+
"property_name" => "accountId",
|
247
|
+
"operator" => "eq",
|
248
|
+
"property_value" => "123456"
|
249
|
+
}]}).encrypt! # "4d1982fe601b359a5cab7ac7845d3bf27026936cdbf8ce0ab4ebcb6930d6cf7f139e..."
|
250
|
+
```
|
251
|
+
|
252
|
+
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.
|
253
|
+
|
254
|
+
### Troubleshooting
|
255
|
+
|
256
|
+
##### EventMachine
|
257
|
+
|
258
|
+
If you run into `Keen::Error: Keen IO Exception: An EventMachine loop must be running to use publish_async calls` or
|
259
|
+
`Uncaught RuntimeError: eventmachine not initialized: evma_set_pending_connect_timeout`, this means that the EventMachine
|
260
|
+
loop has died. This can happen for a variety of reasons, and every app is different. [Issue #22](https://github.com/keenlabs/keen-gem/issues/22) shows how to add some extra protection to avoid this situation.
|
261
|
+
|
262
|
+
##### publish_async in a script or worker
|
263
|
+
|
264
|
+
If you write a script that uses `publish_async`, you need to keep the script alive long enough for the call(s) to complete.
|
265
|
+
EventMachine itself won't do this because it runs in a different thread. Here's an [example gist](https://gist.github.com/dzello/7472823) that shows how to exit the process after the event has been recorded.
|
237
266
|
|
238
267
|
### Changelog
|
239
268
|
|
269
|
+
##### 0.8.0
|
270
|
+
+ **UPGRADE WARNING** Do you use spaces in collection names? Or other special characters? Read [this post](https://groups.google.com/forum/?fromgroups#!topic/keen-io-devs/VtCgPuNKrgY) from the mailing list to make sure your collection names don't change.
|
271
|
+
+ Add support for generating [scoped keys](https://keen.io/docs/security/#scoped-key).
|
272
|
+
+ Make collection name encoding more robust. Make sure collection names are encoded identically for publishing events, running queries, and performing deletes.
|
273
|
+
+ Add support for [grouping by multiple properties](https://keen.io/docs/data-analysis/group-by/#grouping-by-multiple-properties).
|
274
|
+
|
275
|
+
##### 0.7.8
|
276
|
+
+ Add support for redirect URL creation.
|
277
|
+
|
240
278
|
##### 0.7.7
|
241
279
|
+ Add support for HTTP and SOCKS proxies. Set `KEEN_PROXY_URL` to the proxy URL and `KEEN_PROXY_TYPE` to 'socks5' if you need to. These
|
242
280
|
properties can also be set on the client instances as `proxy_url` and `proxy_type`.
|
@@ -299,6 +337,22 @@ at [users.keen.io](http://users.keen.io). We'd love to hear your feedback and id
|
|
299
337
|
keen-gem is an open source project and we welcome your contributions.
|
300
338
|
Fire away with issues and pull requests!
|
301
339
|
|
340
|
+
#### Running Tests
|
341
|
+
|
342
|
+
`bundle exec rake spec` - Run unit specs. HTTP is mocked.
|
343
|
+
|
344
|
+
`bundle exec rake integration` - Run integration specs with the real API. Requires env variables. See [.travis.yml](https://github.com/keenlabs/keen-gem/blob/master/.travis.yml).
|
345
|
+
|
346
|
+
`bundle exec rake synchrony` - Run async publishing specs with `EM::Synchrony`.
|
347
|
+
|
348
|
+
Similarly, you can use guard to listen for changes to files and run specs.
|
349
|
+
|
350
|
+
`bundle exec guard -g unit`
|
351
|
+
|
352
|
+
`bundle exec guard -g integration`
|
353
|
+
|
354
|
+
`bundle exec guard -g synchrony`
|
355
|
+
|
302
356
|
### Community Contributors
|
303
357
|
+ [alexkwolfe](https://github.com/alexkwolfe)
|
304
358
|
+ [peteygao](https://github.com/peteygao)
|
data/keen.gemspec
CHANGED
@@ -10,24 +10,32 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.homepage = "https://github.com/keenlabs/keen-gem"
|
11
11
|
s.summary = "Keen IO API Client"
|
12
12
|
s.description = "Send events and build analytics features into your Ruby applications."
|
13
|
+
s.license = "MIT"
|
14
|
+
|
15
|
+
s.post_install_message = "**UPGRADE WARNING** Do you use spaces in collection names? Or other special characters? Read https://groups.google.com/forum/?fromgroups#!topic/keen-io-devs/VtCgPuNKrgY from the mailing list to make sure your collection names don't change!"
|
13
16
|
|
14
17
|
s.add_dependency "multi_json", "~> 1.0"
|
18
|
+
s.add_dependency "addressable", "~> 2.3.5"
|
15
19
|
s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
|
16
20
|
|
21
|
+
s.add_dependency 'rubysl', '~> 2.0' if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
22
|
+
|
17
23
|
# guard
|
18
|
-
|
19
|
-
|
24
|
+
unless RUBY_VERSION.start_with? '1.8'
|
25
|
+
s.add_development_dependency 'guard'
|
26
|
+
s.add_development_dependency 'guard-rspec'
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
# guard cross-platform listener trick
|
29
|
+
s.add_development_dependency 'rb-inotify'
|
30
|
+
s.add_development_dependency 'rb-fsevent'
|
31
|
+
s.add_development_dependency 'rb-fchange'
|
25
32
|
|
26
|
-
|
27
|
-
|
33
|
+
# guard notifications
|
34
|
+
s.add_development_dependency 'ruby_gntp'
|
28
35
|
|
29
|
-
|
30
|
-
|
36
|
+
# fix guard prompt
|
37
|
+
s.add_development_dependency 'rb-readline' # or compile ruby w/ readline
|
38
|
+
end
|
31
39
|
|
32
40
|
# debuggers
|
33
41
|
if /\Aruby/ === RUBY_DESCRIPTION
|
data/lib/keen.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Keen
|
6
|
+
module AESHelper
|
7
|
+
|
8
|
+
BLOCK_SIZE = 32
|
9
|
+
|
10
|
+
def aes256_encrypt(key, plaintext)
|
11
|
+
aes = OpenSSL::Cipher::AES.new(256, :CBC)
|
12
|
+
aes.encrypt
|
13
|
+
aes.key = key
|
14
|
+
iv = aes.random_iv
|
15
|
+
[aes.update(plaintext) + aes.final, iv]
|
16
|
+
end
|
17
|
+
|
18
|
+
def aes256_decrypt(key, iv_plus_encrypted)
|
19
|
+
iv = iv_plus_encrypted[0, 16]
|
20
|
+
encrypted = iv_plus_encrypted[16, iv_plus_encrypted.length]
|
21
|
+
aes = OpenSSL::Cipher::AES.new(256, :CBC)
|
22
|
+
aes.decrypt
|
23
|
+
aes.key = key
|
24
|
+
aes.iv = iv
|
25
|
+
aes.update(encrypted) + aes.final
|
26
|
+
end
|
27
|
+
|
28
|
+
def hexlify(msg)
|
29
|
+
msg.unpack('H*')[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def unhexlify(msg)
|
33
|
+
[msg].pack('H*')
|
34
|
+
end
|
35
|
+
|
36
|
+
def pad(msg)
|
37
|
+
pad_len = BLOCK_SIZE - (msg.length % BLOCK_SIZE)
|
38
|
+
padding = pad_len.chr * pad_len
|
39
|
+
padded = msg + padding
|
40
|
+
padded
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/keen/client.rb
CHANGED
@@ -3,11 +3,11 @@ 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
|
-
|
7
6
|
require 'openssl'
|
8
7
|
require 'multi_json'
|
9
8
|
require 'base64'
|
10
9
|
require 'cgi'
|
10
|
+
require 'addressable/uri'
|
11
11
|
|
12
12
|
module Keen
|
13
13
|
class Client
|
@@ -92,25 +92,15 @@ module Keen
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def api_event_collection_resource_path(event_collection)
|
95
|
-
|
95
|
+
encoded_collection_name = Addressable::URI.escape(event_collection.to_s)
|
96
|
+
encoded_collection_name.gsub! '/', '%2F'
|
97
|
+
"/#{api_version}/projects/#{project_id}/events/#{encoded_collection_name}"
|
96
98
|
end
|
97
99
|
|
98
100
|
def preprocess_params(params)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if params.key?(:steps)
|
104
|
-
params[:steps] = MultiJson.encode(params[:steps])
|
105
|
-
end
|
106
|
-
|
107
|
-
if params.key?(:analyses)
|
108
|
-
params[:analyses] = MultiJson.encode(params[:analyses])
|
109
|
-
end
|
110
|
-
|
111
|
-
if params.key?(:timeframe) && params[:timeframe].is_a?(Hash)
|
112
|
-
params[:timeframe] = MultiJson.encode(params[:timeframe])
|
113
|
-
end
|
101
|
+
preprocess_encodables(params)
|
102
|
+
preprocess_timeframe(params)
|
103
|
+
preprocess_group_by(params)
|
114
104
|
|
115
105
|
query_params = ""
|
116
106
|
params.each do |param, value|
|
@@ -121,6 +111,28 @@ module Keen
|
|
121
111
|
query_params
|
122
112
|
end
|
123
113
|
|
114
|
+
def preprocess_encodables(params)
|
115
|
+
[:filters, :steps, :analyses].each do |key|
|
116
|
+
if params.key?(key)
|
117
|
+
params[key] = MultiJson.encode(params[key])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def preprocess_timeframe(params)
|
123
|
+
timeframe = params[:timeframe]
|
124
|
+
if timeframe.is_a?(Hash)
|
125
|
+
params[:timeframe] = MultiJson.encode(timeframe)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def preprocess_group_by(params)
|
130
|
+
group_by = params[:group_by]
|
131
|
+
if group_by.is_a?(Array)
|
132
|
+
params[:group_by] = MultiJson.encode(group_by)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
124
136
|
def method_missing(_method, *args, &block)
|
125
137
|
if config = CONFIG[_method.to_sym]
|
126
138
|
if config.is_a?(Proc)
|
@@ -61,8 +61,6 @@ module Keen
|
|
61
61
|
ensure_write_key!
|
62
62
|
check_event_data!(event_collection, properties)
|
63
63
|
|
64
|
-
deferrable = EventMachine::DefaultDeferrable.new
|
65
|
-
|
66
64
|
http_client = Keen::HTTP::Async.new(
|
67
65
|
self.api_url,
|
68
66
|
{:proxy_url => self.proxy_url, :proxy_type => self.proxy_type})
|
@@ -73,29 +71,9 @@ module Keen
|
|
73
71
|
)
|
74
72
|
|
75
73
|
if defined?(EM::Synchrony)
|
76
|
-
|
77
|
-
error = HttpError.new("HTTP em-synchrony publish_async error: #{http.error}")
|
78
|
-
Keen.logger.error(error)
|
79
|
-
raise error
|
80
|
-
else
|
81
|
-
process_response(http.response_header.status, http.response.chomp)
|
82
|
-
end
|
74
|
+
process_with_synchrony(http)
|
83
75
|
else
|
84
|
-
http
|
85
|
-
begin
|
86
|
-
response = process_response(http.response_header.status, http.response.chomp)
|
87
|
-
rescue Exception => e
|
88
|
-
Keen.logger.error(e)
|
89
|
-
deferrable.fail(e)
|
90
|
-
end
|
91
|
-
deferrable.succeed(response) if response
|
92
|
-
}
|
93
|
-
http.errback {
|
94
|
-
error = Error.new("HTTP publish_async failure: #{http.error}")
|
95
|
-
Keen.logger.error(error)
|
96
|
-
deferrable.fail(error)
|
97
|
-
}
|
98
|
-
deferrable
|
76
|
+
process_with_callbacks(http)
|
99
77
|
end
|
100
78
|
end
|
101
79
|
|
@@ -131,6 +109,35 @@ module Keen
|
|
131
109
|
|
132
110
|
private
|
133
111
|
|
112
|
+
def process_with_synchrony(http)
|
113
|
+
if http.error
|
114
|
+
error = HttpError.new("HTTP em-synchrony publish_async error: #{http.error}")
|
115
|
+
Keen.logger.error(error)
|
116
|
+
raise error
|
117
|
+
else
|
118
|
+
process_response(http.response_header.status, http.response.chomp)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def process_with_callbacks(http)
|
123
|
+
deferrable = EventMachine::DefaultDeferrable.new
|
124
|
+
http.callback {
|
125
|
+
begin
|
126
|
+
response = process_response(http.response_header.status, http.response.chomp)
|
127
|
+
rescue Exception => e
|
128
|
+
Keen.logger.error(e)
|
129
|
+
deferrable.fail(e)
|
130
|
+
end
|
131
|
+
deferrable.succeed(response) if response
|
132
|
+
}
|
133
|
+
http.errback {
|
134
|
+
error = Error.new("HTTP publish_async failure: #{http.error}")
|
135
|
+
Keen.logger.error(error)
|
136
|
+
deferrable.fail(error)
|
137
|
+
}
|
138
|
+
deferrable
|
139
|
+
end
|
140
|
+
|
134
141
|
def publish_body(path, body, error_method)
|
135
142
|
begin
|
136
143
|
response = Keen::HTTP::Sync.new(
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'keen/aes_helper'
|
3
|
+
|
4
|
+
module Keen
|
5
|
+
class ScopedKey
|
6
|
+
include AESHelper
|
7
|
+
extend AESHelper
|
8
|
+
|
9
|
+
attr_accessor :api_key
|
10
|
+
attr_accessor :data
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def decrypt!(api_key, scoped_key)
|
14
|
+
encrypted = unhexlify(scoped_key)
|
15
|
+
padded_api_key = pad(api_key)
|
16
|
+
decrypted = aes256_decrypt(padded_api_key, encrypted)
|
17
|
+
data = MultiJson.load(decrypted)
|
18
|
+
self.new(api_key, data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(api_key, data)
|
23
|
+
self.api_key = api_key
|
24
|
+
self.data = data
|
25
|
+
end
|
26
|
+
|
27
|
+
def encrypt!
|
28
|
+
json_str = MultiJson.dump(self.data)
|
29
|
+
padded_api_key = pad(self.api_key)
|
30
|
+
padded_data = pad(json_str)
|
31
|
+
encrypted, iv = aes256_encrypt(padded_api_key, json_str)
|
32
|
+
hexlify(iv) + hexlify(encrypted)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/keen/version.rb
CHANGED
@@ -5,7 +5,7 @@ describe "Keen IO API" do
|
|
5
5
|
let(:write_key) { ENV['KEEN_WRITE_KEY'] }
|
6
6
|
|
7
7
|
describe "publishing" do
|
8
|
-
let(:collection) { "
|
8
|
+
let(:collection) { "User posts.new" }
|
9
9
|
let(:event_properties) { { "name" => "Bob" } }
|
10
10
|
let(:api_success) { { "created" => true } }
|
11
11
|
|
@@ -116,6 +116,24 @@ describe "Keen IO API" do
|
|
116
116
|
it "should return a valid count_unique" do
|
117
117
|
Keen.count_unique(event_collection, :target_property => "price").should == 2
|
118
118
|
end
|
119
|
+
|
120
|
+
it "should return a valid count with group_by" do
|
121
|
+
response = Keen.average(event_collection, :group_by => "username", :target_property => "price")
|
122
|
+
bobs_response = response.select { |result| result["username"] == "bob" }.first
|
123
|
+
bobs_response["result"].should == 10
|
124
|
+
teds_response = response.select { |result| result["username"] == "ted" }.first
|
125
|
+
teds_response["result"].should == 20
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should return a valid count with multi-group_by" do
|
129
|
+
response = Keen.average(event_collection, :group_by => ["username", "price"], :target_property => "price")
|
130
|
+
bobs_response = response.select { |result| result["username"] == "bob" }.first
|
131
|
+
bobs_response["result"].should == 10
|
132
|
+
bobs_response["price"].should == 10
|
133
|
+
teds_response = response.select { |result| result["username"] == "ted" }.first
|
134
|
+
teds_response["result"].should == 20
|
135
|
+
teds_response["price"].should == 20
|
136
|
+
end
|
119
137
|
|
120
138
|
it "should return a valid sum" do
|
121
139
|
Keen.sum(event_collection, :target_property => "price").should == 30
|
@@ -171,7 +189,7 @@ describe "Keen IO API" do
|
|
171
189
|
before do
|
172
190
|
Keen.publish(event_collection, :delete => "me")
|
173
191
|
Keen.publish(event_collection, :delete => "you")
|
174
|
-
sleep(
|
192
|
+
sleep(10)
|
175
193
|
end
|
176
194
|
|
177
195
|
it "should delete the event" do
|
@@ -4,7 +4,7 @@ describe Keen::Client::PublishingMethods do
|
|
4
4
|
let(:project_id) { "12345" }
|
5
5
|
let(:write_key) { "abcde" }
|
6
6
|
let(:api_url) { "https://unreal.keen.io" }
|
7
|
-
let(:collection) { "
|
7
|
+
let(:collection) { "some :actions_to.record" }
|
8
8
|
let(:event_properties) { { "name" => "Bob" } }
|
9
9
|
let(:api_success) { { "created" => true } }
|
10
10
|
let(:client) { Keen::Client.new(
|
@@ -37,9 +37,9 @@ describe Keen::Client::PublishingMethods do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it "should url encode the event collection" do
|
40
|
-
stub_keen_post(api_event_collection_resource_url(api_url, "
|
41
|
-
client.publish("
|
42
|
-
expect_keen_post(api_event_collection_resource_url(api_url, "
|
40
|
+
stub_keen_post(api_event_collection_resource_url(api_url, "User%20posts.new%20)(*%26%5E%25%40!)%3A%3A%2520%2520"), 201, "")
|
41
|
+
client.publish("User posts.new )(*&^%@!)::%20%20", event_properties)
|
42
|
+
expect_keen_post(api_event_collection_resource_url(api_url, "User%20posts.new%20)(*%26%5E%25%40!)%3A%3A%2520%2520"), event_properties, "sync", write_key)
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should wrap exceptions" do
|
@@ -138,11 +138,11 @@ describe Keen::Client::PublishingMethods do
|
|
138
138
|
end
|
139
139
|
|
140
140
|
it "should url encode the event collection" do
|
141
|
-
stub_keen_post(api_event_collection_resource_url(api_url,
|
141
|
+
stub_keen_post(api_event_collection_resource_url(api_url, 'User%20posts.new%20)(*%26%5E%25%40!)%3A%3A%2520%2520'), 201, api_success)
|
142
142
|
EM.run {
|
143
|
-
client.publish_async(
|
143
|
+
client.publish_async('User posts.new )(*&^%@!)::%20%20', event_properties).callback {
|
144
144
|
begin
|
145
|
-
expect_keen_post(api_event_collection_resource_url(api_url,
|
145
|
+
expect_keen_post(api_event_collection_resource_url(api_url, 'User%20posts.new%20)(*%26%5E%25%40!)%3A%3A%2520%2520'), event_properties, "async", write_key)
|
146
146
|
ensure
|
147
147
|
EM.stop
|
148
148
|
end
|
@@ -75,6 +75,16 @@ describe Keen::Client do
|
|
75
75
|
filter_str = CGI.escape(MultiJson.encode(filters))
|
76
76
|
test_query("&filters=#{filter_str}", :filters => filters)
|
77
77
|
end
|
78
|
+
|
79
|
+
it "should encode a single group by property" do
|
80
|
+
test_query("&group_by=one%20foo", :group_by => "one foo")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should encode multi-group by properly" do
|
84
|
+
group_by = ["one", "two"]
|
85
|
+
group_by_str = CGI.escape(MultiJson.encode(group_by))
|
86
|
+
test_query("&group_by=#{group_by_str}", :group_by => group_by)
|
87
|
+
end
|
78
88
|
|
79
89
|
it "should encode absolute timeframes properly" do
|
80
90
|
timeframe = {
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Keen::ScopedKey do
|
4
|
+
let(:api_key) { "ab428324dbdbcfe744" }
|
5
|
+
let(:bad_api_key) { "badbadbadbad" }
|
6
|
+
let(:data) { {
|
7
|
+
"filters" => [{
|
8
|
+
"property_name" => "accountId",
|
9
|
+
"operator" => "eq",
|
10
|
+
"property_value" => "123456"
|
11
|
+
}]
|
12
|
+
}}
|
13
|
+
let(:new_scoped_key) { Keen::ScopedKey.new(api_key, data) }
|
14
|
+
|
15
|
+
describe "constructor" do
|
16
|
+
it "should retain the api_key" do
|
17
|
+
new_scoped_key.api_key.should == api_key
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should retain the data" do
|
21
|
+
new_scoped_key.data.should == data
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "encrypt! and decrypt!" do
|
26
|
+
it "should encrypt and hex encode the data using the api key" do
|
27
|
+
encrypted_str = new_scoped_key.encrypt!
|
28
|
+
other_api_key = Keen::ScopedKey.decrypt!(api_key, encrypted_str)
|
29
|
+
other_api_key.data.should == data
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not decrypt the scoped key with a bad api key" do
|
33
|
+
encrypted_str = new_scoped_key.encrypt!
|
34
|
+
expect {
|
35
|
+
other_api_key = Keen::ScopedKey.decrypt!(bad_api_key, encrypted_str)
|
36
|
+
}.to raise_error(OpenSSL::Cipher::CipherError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.8.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Kyle Wild
|
@@ -11,12 +10,11 @@ authors:
|
|
11
10
|
autorequire:
|
12
11
|
bindir: bin
|
13
12
|
cert_chain: []
|
14
|
-
date:
|
13
|
+
date: 2014-01-09 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: multi_json
|
18
17
|
requirement: !ruby/object:Gem::Requirement
|
19
|
-
none: false
|
20
18
|
requirements:
|
21
19
|
- - ~>
|
22
20
|
- !ruby/object:Gem::Version
|
@@ -24,137 +22,120 @@ dependencies:
|
|
24
22
|
type: :runtime
|
25
23
|
prerelease: false
|
26
24
|
version_requirements: !ruby/object:Gem::Requirement
|
27
|
-
none: false
|
28
25
|
requirements:
|
29
26
|
- - ~>
|
30
27
|
- !ruby/object:Gem::Version
|
31
28
|
version: '1.0'
|
32
29
|
- !ruby/object:Gem::Dependency
|
33
|
-
name:
|
30
|
+
name: addressable
|
34
31
|
requirement: !ruby/object:Gem::Requirement
|
35
|
-
none: false
|
36
32
|
requirements:
|
37
|
-
- -
|
33
|
+
- - ~>
|
38
34
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
40
|
-
type: :
|
35
|
+
version: 2.3.5
|
36
|
+
type: :runtime
|
41
37
|
prerelease: false
|
42
38
|
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
none: false
|
44
39
|
requirements:
|
45
|
-
- -
|
40
|
+
- - ~>
|
46
41
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
42
|
+
version: 2.3.5
|
48
43
|
- !ruby/object:Gem::Dependency
|
49
|
-
name: guard
|
44
|
+
name: guard
|
50
45
|
requirement: !ruby/object:Gem::Requirement
|
51
|
-
none: false
|
52
46
|
requirements:
|
53
|
-
- -
|
47
|
+
- - '>='
|
54
48
|
- !ruby/object:Gem::Version
|
55
49
|
version: '0'
|
56
50
|
type: :development
|
57
51
|
prerelease: false
|
58
52
|
version_requirements: !ruby/object:Gem::Requirement
|
59
|
-
none: false
|
60
53
|
requirements:
|
61
|
-
- -
|
54
|
+
- - '>='
|
62
55
|
- !ruby/object:Gem::Version
|
63
56
|
version: '0'
|
64
57
|
- !ruby/object:Gem::Dependency
|
65
|
-
name:
|
58
|
+
name: guard-rspec
|
66
59
|
requirement: !ruby/object:Gem::Requirement
|
67
|
-
none: false
|
68
60
|
requirements:
|
69
|
-
- -
|
61
|
+
- - '>='
|
70
62
|
- !ruby/object:Gem::Version
|
71
63
|
version: '0'
|
72
64
|
type: :development
|
73
65
|
prerelease: false
|
74
66
|
version_requirements: !ruby/object:Gem::Requirement
|
75
|
-
none: false
|
76
67
|
requirements:
|
77
|
-
- -
|
68
|
+
- - '>='
|
78
69
|
- !ruby/object:Gem::Version
|
79
70
|
version: '0'
|
80
71
|
- !ruby/object:Gem::Dependency
|
81
|
-
name: rb-
|
72
|
+
name: rb-inotify
|
82
73
|
requirement: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
74
|
requirements:
|
85
|
-
- -
|
75
|
+
- - '>='
|
86
76
|
- !ruby/object:Gem::Version
|
87
77
|
version: '0'
|
88
78
|
type: :development
|
89
79
|
prerelease: false
|
90
80
|
version_requirements: !ruby/object:Gem::Requirement
|
91
|
-
none: false
|
92
81
|
requirements:
|
93
|
-
- -
|
82
|
+
- - '>='
|
94
83
|
- !ruby/object:Gem::Version
|
95
84
|
version: '0'
|
96
85
|
- !ruby/object:Gem::Dependency
|
97
|
-
name: rb-
|
86
|
+
name: rb-fsevent
|
98
87
|
requirement: !ruby/object:Gem::Requirement
|
99
|
-
none: false
|
100
88
|
requirements:
|
101
|
-
- -
|
89
|
+
- - '>='
|
102
90
|
- !ruby/object:Gem::Version
|
103
91
|
version: '0'
|
104
92
|
type: :development
|
105
93
|
prerelease: false
|
106
94
|
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
none: false
|
108
95
|
requirements:
|
109
|
-
- -
|
96
|
+
- - '>='
|
110
97
|
- !ruby/object:Gem::Version
|
111
98
|
version: '0'
|
112
99
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
100
|
+
name: rb-fchange
|
114
101
|
requirement: !ruby/object:Gem::Requirement
|
115
|
-
none: false
|
116
102
|
requirements:
|
117
|
-
- -
|
103
|
+
- - '>='
|
118
104
|
- !ruby/object:Gem::Version
|
119
105
|
version: '0'
|
120
106
|
type: :development
|
121
107
|
prerelease: false
|
122
108
|
version_requirements: !ruby/object:Gem::Requirement
|
123
|
-
none: false
|
124
109
|
requirements:
|
125
|
-
- -
|
110
|
+
- - '>='
|
126
111
|
- !ruby/object:Gem::Version
|
127
112
|
version: '0'
|
128
113
|
- !ruby/object:Gem::Dependency
|
129
|
-
name:
|
114
|
+
name: ruby_gntp
|
130
115
|
requirement: !ruby/object:Gem::Requirement
|
131
|
-
none: false
|
132
116
|
requirements:
|
133
|
-
- -
|
117
|
+
- - '>='
|
134
118
|
- !ruby/object:Gem::Version
|
135
119
|
version: '0'
|
136
120
|
type: :development
|
137
121
|
prerelease: false
|
138
122
|
version_requirements: !ruby/object:Gem::Requirement
|
139
|
-
none: false
|
140
123
|
requirements:
|
141
|
-
- -
|
124
|
+
- - '>='
|
142
125
|
- !ruby/object:Gem::Version
|
143
126
|
version: '0'
|
144
127
|
- !ruby/object:Gem::Dependency
|
145
|
-
name:
|
128
|
+
name: rb-readline
|
146
129
|
requirement: !ruby/object:Gem::Requirement
|
147
|
-
none: false
|
148
130
|
requirements:
|
149
|
-
- -
|
131
|
+
- - '>='
|
150
132
|
- !ruby/object:Gem::Version
|
151
133
|
version: '0'
|
152
134
|
type: :development
|
153
135
|
prerelease: false
|
154
136
|
version_requirements: !ruby/object:Gem::Requirement
|
155
|
-
none: false
|
156
137
|
requirements:
|
157
|
-
- -
|
138
|
+
- - '>='
|
158
139
|
- !ruby/object:Gem::Version
|
159
140
|
version: '0'
|
160
141
|
description: Send events and build analytics features into your Ruby applications.
|
@@ -174,11 +155,13 @@ files:
|
|
174
155
|
- config/cacert.pem
|
175
156
|
- keen.gemspec
|
176
157
|
- lib/keen.rb
|
158
|
+
- lib/keen/aes_helper.rb
|
177
159
|
- lib/keen/client.rb
|
178
160
|
- lib/keen/client/maintenance_methods.rb
|
179
161
|
- lib/keen/client/publishing_methods.rb
|
180
162
|
- lib/keen/client/querying_methods.rb
|
181
163
|
- lib/keen/http.rb
|
164
|
+
- lib/keen/scoped_key.rb
|
182
165
|
- lib/keen/version.rb
|
183
166
|
- spec/integration/api_spec.rb
|
184
167
|
- spec/integration/spec_helper.rb
|
@@ -187,33 +170,36 @@ files:
|
|
187
170
|
- spec/keen/client/querying_methods_spec.rb
|
188
171
|
- spec/keen/client_spec.rb
|
189
172
|
- spec/keen/keen_spec.rb
|
173
|
+
- spec/keen/scoped_key_spec.rb
|
190
174
|
- spec/keen/spec_helper.rb
|
191
175
|
- spec/spec_helper.rb
|
192
176
|
- spec/synchrony/spec_helper.rb
|
193
177
|
- spec/synchrony/synchrony_spec.rb
|
194
178
|
homepage: https://github.com/keenlabs/keen-gem
|
195
|
-
licenses:
|
196
|
-
|
179
|
+
licenses:
|
180
|
+
- MIT
|
181
|
+
metadata: {}
|
182
|
+
post_install_message: '**UPGRADE WARNING** Do you use spaces in collection names?
|
183
|
+
Or other special characters? Read https://groups.google.com/forum/?fromgroups#!topic/keen-io-devs/VtCgPuNKrgY
|
184
|
+
from the mailing list to make sure your collection names don''t change!'
|
197
185
|
rdoc_options: []
|
198
186
|
require_paths:
|
199
187
|
- lib
|
200
188
|
required_ruby_version: !ruby/object:Gem::Requirement
|
201
|
-
none: false
|
202
189
|
requirements:
|
203
|
-
- -
|
190
|
+
- - '>='
|
204
191
|
- !ruby/object:Gem::Version
|
205
192
|
version: '0'
|
206
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
207
|
-
none: false
|
208
194
|
requirements:
|
209
|
-
- -
|
195
|
+
- - '>='
|
210
196
|
- !ruby/object:Gem::Version
|
211
197
|
version: '0'
|
212
198
|
requirements: []
|
213
199
|
rubyforge_project:
|
214
|
-
rubygems_version:
|
200
|
+
rubygems_version: 2.0.3
|
215
201
|
signing_key:
|
216
|
-
specification_version:
|
202
|
+
specification_version: 4
|
217
203
|
summary: Keen IO API Client
|
218
204
|
test_files:
|
219
205
|
- spec/integration/api_spec.rb
|
@@ -223,7 +209,9 @@ test_files:
|
|
223
209
|
- spec/keen/client/querying_methods_spec.rb
|
224
210
|
- spec/keen/client_spec.rb
|
225
211
|
- spec/keen/keen_spec.rb
|
212
|
+
- spec/keen/scoped_key_spec.rb
|
226
213
|
- spec/keen/spec_helper.rb
|
227
214
|
- spec/spec_helper.rb
|
228
215
|
- spec/synchrony/spec_helper.rb
|
229
216
|
- spec/synchrony/synchrony_spec.rb
|
217
|
+
has_rdoc:
|