keen 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,7 @@ bundler_args: --without development
4
4
  rvm:
5
5
  - 1.8.7
6
6
  - 1.9.3
7
+ - 2.0.0
7
8
  - jruby-19mode
8
9
  - rbx-19mode
9
10
 
data/README.md CHANGED
@@ -37,7 +37,9 @@ environment-based approach because it keeps sensitive information out of the cod
37
37
 
38
38
  If your environment is set up property, `Keen` is ready go immediately. Publish an event like this:
39
39
 
40
- Keen.publish("sign_ups", { :username => "lloyd", :referred_by => "harry" })
40
+ ```ruby
41
+ Keen.publish("sign_ups", { :username => "lloyd", :referred_by => "harry" })
42
+ ```
41
43
 
42
44
  This will publish an event to the 'sign_ups' collection with the `username` and `referred_by` properties set.
43
45
 
@@ -63,33 +65,78 @@ To publish asynchronously, first add
63
65
  Next, run an instance of EventMachine. If you're using an EventMachine-based web server like
64
66
  thin or goliath you're already doing this. Otherwise, you'll need to start an EventMachine loop manually as follows:
65
67
 
66
- Thread.new { EventMachine.run }
68
+ ```ruby
69
+ Thread.new { EventMachine.run }
70
+ ```
67
71
 
68
72
  The best place for this is in an initializer, or anywhere that runs when your app boots up.
69
73
  Here's a good blog article that explains more about this approach - [EventMachine and Passenger](http://railstips.org/blog/archives/2011/05/04/eventmachine-and-passenger/).
70
74
 
75
+ And here's a gist that shows an example of [Eventmachine with Unicorn](https://gist.github.com/jonkgrimes/5103321). Thanks to [jonkgrimes](https://github.com/jonkgrimes) for sharing this with us!
76
+
71
77
  Now, in your code, replace `publish` with `publish_async`. Bind callbacks if you require them.
72
78
 
73
- http = Keen.publish_async("sign_ups", { :username => "lloyd", :referred_by => "harry" })
74
- http.callback { |response| puts "Success: #{response}"}
75
- http.errback { puts "was a failurrr :,(" }
79
+ ```ruby
80
+ http = Keen.publish_async("sign_ups", { :username => "lloyd", :referred_by => "harry" })
81
+ http.callback { |response| puts "Success: #{response}"}
82
+ http.errback { puts "was a failurrr :,(" }
83
+ ```
76
84
 
77
85
  This will schedule the network call into the event loop and allow your request thread
78
86
  to resume processing immediately.
79
87
 
88
+ ### Running queries
89
+
90
+ The Keen IO API provides rich querying capabilities against your event data set. For more information, see the [Data Analysis API Guide](https://keen.io/docs/data-analysis/).
91
+
92
+ Unlike event publishing, queries require that an API Key is provided. Just like project ID, we encourage that you set this as an environment variable:
93
+
94
+ KEEN_API_KEY=your-api-key
95
+
96
+ Here's are some examples of querying with the Ruby gem. Let's assume you've added some events to the "purchases" collection.
97
+
98
+ ```ruby
99
+ Keen.count("purchases") # => 100
100
+ Keen.sum("purchases", :target_property => "price") # => 10000
101
+ Keen.minimum("purchases", :target_property => "price") # => 20
102
+ Keen.maximum("purchases", :target_property => "price") # => 100
103
+ Keen.average("purchases", :target_property => "price") # => 60
104
+
105
+ Keen.sum("purchases", :target_property => "price", :group_by => "item.id") # => [{ "item.id": 123, "result": 240 }, { ... }]
106
+
107
+ Keen.count_unique("purchases", :target_property => "username") # => 3
108
+ Keen.select_unique("purchases", :target_property => "username") # => ["bob", "linda", "travis"]
109
+
110
+ Keen.extraction("purchases") # => [{ "price" => 20, ... }, { ... }]
111
+
112
+ Keen.funnel(:steps => [
113
+ { :actor_property => "username", "event_collection" => "purchases" },
114
+ { :actor_property => "username", "event_collection" => "referrals" },
115
+ { ... }]) # => [20, 15 ...]
116
+ ```
117
+
118
+ Many of there queries can be performed with group by, filters, series and intervals. The API response for these is converted directly into Ruby Hash or Array.
119
+
120
+ Detailed information on available parameters for each API resource can be found on the [API Technical Reference](https://keen.io/docs/api/reference/).
121
+
80
122
  ### Other code examples
81
123
 
82
124
  #### Authentication
83
125
 
84
126
  To configure keen-gem in code, do as follows:
85
127
 
86
- Keen.project_id = 'your-project-id'
128
+ ```ruby
129
+ Keen.project_id = 'your-project-id'
130
+ ```
87
131
 
88
132
  You can also configure individual client instances as follows:
89
133
 
90
- keen = Keen::Client.new(:project_id => 'your-project-id')
134
+ ```ruby
135
+ keen = Keen::Client.new(:project_id => 'your-project-id')
136
+ ```
91
137
 
92
138
  #### em-synchrony
139
+
93
140
  keen-gem can be used with [em-synchrony](https://github.com/igrigorik/em-synchrony).
94
141
  If you call `publish_async` and `EM::Synchrony` is defined the method will return the response
95
142
  directly. (It does not return the deferrable on which to register callbacks.) Likewise, it will raise
@@ -103,15 +150,18 @@ This is useful for situations like tracking email opens using [image beacons](ht
103
150
  In this situation, the JSON event data is passed by encoding it base-64 and adding it as a request parameter called `data`.
104
151
  The `beacon_url` method found on the `Keen::Client` does this for you. Here's an example:
105
152
 
106
- keen = Keen::Client.new(:project_id => '12345')
107
-
108
- keen.beacon_url("sign_ups", :recipient => "foo@foo.com")
109
- # => "https://api.keen.io/3.0/projects/12345/events/email_opens?data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9"
153
+ ```ruby
154
+ Keen.beacon_url("sign_ups", :recipient => "foo@foo.com")
155
+ # => "https://api.keen.io/3.0/projects/12345/events/email_opens?data=eyJyZWNpcGllbnQiOiJmb29AZm9vLmNvbSJ9"
156
+ ```
110
157
 
111
158
  To track email opens, simply add an image to your email template that points to this URL.
112
159
 
113
160
  ### Changelog
114
161
 
162
+ ##### 0.6.0
163
+ + Added querying capabilities. A big thanks to [ifeelgoods](http://www.ifeelgoods.com/) for contributing!
164
+
115
165
  ##### 0.5.0
116
166
  + Removed API Key as a required field on Keen::Client. Only the Project ID is required to publish events.
117
167
  + You can continue to provide the API Key. Future features planned for this gem will require it. But for now,
@@ -23,7 +23,8 @@ module Keen
23
23
 
24
24
  def_delegators :default_client, :project_id, :api_key,
25
25
  :project_id=, :api_key=, :publish, :publish_async,
26
- :beacon_url
26
+ :beacon_url, :count, :count_unique, :minimum, :maximum,
27
+ :sum, :average, :select_unique, :funnel, :extraction
27
28
 
28
29
  attr_writer :logger
29
30
 
@@ -1,5 +1,8 @@
1
1
  require 'keen/http'
2
2
  require 'keen/version'
3
+ require 'keen/client/publishing_methods'
4
+ require 'keen/client/querying_methods'
5
+
3
6
  require 'openssl'
4
7
  require 'multi_json'
5
8
  require 'base64'
@@ -7,6 +10,9 @@ require 'uri'
7
10
 
8
11
  module Keen
9
12
  class Client
13
+ include Keen::Client::PublishingMethods
14
+ include Keen::Client::QueryingMethods
15
+
10
16
  attr_accessor :project_id, :api_key
11
17
 
12
18
  CONFIG = {
@@ -30,12 +36,6 @@ module Keen
30
36
  }
31
37
  }
32
38
 
33
- def beacon_url(event_collection, properties)
34
- json = MultiJson.encode(properties)
35
- data = [json].pack("m0").tr("+/", "-_").gsub("\n", "")
36
- "https://#{api_host}#{api_path(event_collection)}?data=#{data}"
37
- end
38
-
39
39
  def initialize(*args)
40
40
  options = args[0]
41
41
  unless options.is_a?(Hash)
@@ -50,64 +50,6 @@ module Keen
50
50
  :project_id, :api_key)
51
51
  end
52
52
 
53
- def publish(event_collection, properties)
54
- check_configuration!
55
- check_event_data!(event_collection, properties)
56
-
57
- begin
58
- response = Keen::HTTP::Sync.new(
59
- api_host, api_port, api_sync_http_options).post(
60
- :path => api_path(event_collection),
61
- :headers => api_headers_with_auth("sync"),
62
- :body => MultiJson.encode(properties))
63
- rescue Exception => http_error
64
- raise HttpError.new("Couldn't connect to Keen IO: #{http_error.message}", http_error)
65
- end
66
- process_response(response.code, response.body.chomp)
67
- end
68
-
69
- def publish_async(event_collection, properties)
70
- check_configuration!
71
- check_event_data!(event_collection, properties)
72
-
73
- deferrable = EventMachine::DefaultDeferrable.new
74
-
75
- http_client = Keen::HTTP::Async.new(api_host, api_port, api_async_http_options)
76
- http = http_client.post({
77
- :path => api_path(event_collection),
78
- :headers => api_headers_with_auth("async"),
79
- :body => MultiJson.encode(properties)
80
- })
81
-
82
- if defined?(EM::Synchrony)
83
- if http.error
84
- Keen.logger.warn("Couldn't connect to Keen IO: #{http.error}")
85
- raise HttpError.new("Couldn't connect to Keen IO: #{http.error}")
86
- else
87
- process_response(http.response_header.status, http.response.chomp)
88
- end
89
- else
90
- http.callback {
91
- begin
92
- response = process_response(http.response_header.status, http.response.chomp)
93
- deferrable.succeed(response)
94
- rescue Exception => e
95
- deferrable.fail(e)
96
- end
97
- }
98
- http.errback {
99
- Keen.logger.warn("Couldn't connect to Keen IO: #{http.error}")
100
- deferrable.fail(Error.new("Couldn't connect to Keen IO: #{http.error}"))
101
- }
102
- deferrable
103
- end
104
- end
105
-
106
- # deprecated
107
- def add_event(event_collection, properties, options={})
108
- self.publish(event_collection, properties, options)
109
- end
110
-
111
53
  private
112
54
 
113
55
  def process_response(status_code, response_body)
@@ -126,21 +68,12 @@ module Keen
126
68
  end
127
69
  end
128
70
 
129
- def api_path(event_collection)
130
- "/#{api_version}/projects/#{project_id}/events/#{URI.escape(event_collection)}"
131
- end
132
-
133
- def api_headers_with_auth(sync_or_async)
134
- api_headers(sync_or_async)
135
- end
136
-
137
- def check_configuration!
138
- raise ConfigurationError, "Project ID must be set" unless project_id
71
+ def ensure_project_id!
72
+ raise ConfigurationError, "Project ID must be set" unless self.project_id
139
73
  end
140
74
 
141
- def check_event_data!(event_collection, properties)
142
- raise ArgumentError, "Event collection can not be nil" unless event_collection
143
- raise ArgumentError, "Event properties can not be nil" unless properties
75
+ def ensure_api_key!
76
+ raise ConfigurationError, "API Key must be set for queries" unless self.api_key
144
77
  end
145
78
 
146
79
  def method_missing(_method, *args, &block)
@@ -0,0 +1,111 @@
1
+ module Keen
2
+ class Client
3
+ module PublishingMethods
4
+
5
+ # @deprecated
6
+ #
7
+ # Publishes a synchronous event
8
+ # @param event_collection
9
+ # @param [Hash] event properties
10
+ #
11
+ # @return the JSON response from the API
12
+ def add_event(event_collection, properties, options={})
13
+ self.publish(event_collection, properties, options)
14
+ end
15
+
16
+ # Publishes a synchronous event
17
+ # See detailed documentation here
18
+ # https://keen.io/docs/api/reference/#event-collection-resource
19
+ #
20
+ # @param event_collection
21
+ # @param [Hash] event properties
22
+ #
23
+ # @return the JSON response from the API
24
+ def publish(event_collection, properties)
25
+ ensure_project_id!
26
+ check_event_data!(event_collection, properties)
27
+
28
+ begin
29
+ response = Keen::HTTP::Sync.new(
30
+ api_host, api_port, api_sync_http_options).post(
31
+ :path => api_event_resource_path(event_collection),
32
+ :headers => api_headers("sync"),
33
+ :body => MultiJson.encode(properties))
34
+ rescue Exception => http_error
35
+ raise HttpError.new("Couldn't connect to Keen IO: #{http_error.message}", http_error)
36
+ end
37
+ process_response(response.code, response.body.chomp)
38
+ end
39
+
40
+ # Publishes an asynchronous event
41
+ # See detailed documentation here
42
+ # https://keen.io/docs/api/reference/#event-collection-resource
43
+ #
44
+ # @param event_collection
45
+ # @param [Hash] event properties
46
+ #
47
+ # @return a deferrable to apply callbacks to
48
+ def publish_async(event_collection, properties)
49
+ ensure_project_id!
50
+ check_event_data!(event_collection, properties)
51
+
52
+ deferrable = EventMachine::DefaultDeferrable.new
53
+
54
+ http_client = Keen::HTTP::Async.new(api_host, api_port, api_async_http_options)
55
+ http = http_client.post({
56
+ :path => api_event_resource_path(event_collection),
57
+ :headers => api_headers("async"),
58
+ :body => MultiJson.encode(properties)
59
+ })
60
+
61
+ if defined?(EM::Synchrony)
62
+ if http.error
63
+ Keen.logger.warn("Couldn't connect to Keen IO: #{http.error}")
64
+ raise HttpError.new("Couldn't connect to Keen IO: #{http.error}")
65
+ else
66
+ process_response(http.response_header.status, http.response.chomp)
67
+ end
68
+ else
69
+ http.callback {
70
+ begin
71
+ response = process_response(http.response_header.status, http.response.chomp)
72
+ deferrable.succeed(response)
73
+ rescue Exception => e
74
+ deferrable.fail(e)
75
+ end
76
+ }
77
+ http.errback {
78
+ Keen.logger.warn("Couldn't connect to Keen IO: #{http.error}")
79
+ deferrable.fail(Error.new("Couldn't connect to Keen IO: #{http.error}"))
80
+ }
81
+ deferrable
82
+ end
83
+ end
84
+
85
+ # Returns an encoded URL that will record an event. Useful in email situations.
86
+ # See detailed documentation here
87
+ # https://keen.io/docs/api/reference/#event-collection-resource
88
+ #
89
+ # @param event_collection
90
+ # @param [Hash] event properties
91
+ #
92
+ # @return a URL that will track an event when hit
93
+ def beacon_url(event_collection, properties)
94
+ json = MultiJson.encode(properties)
95
+ data = [json].pack("m0").tr("+/", "-_").gsub("\n", "")
96
+ "https://#{api_host}#{api_event_resource_path(event_collection)}?data=#{data}"
97
+ end
98
+
99
+ private
100
+
101
+ def api_event_resource_path(event_collection)
102
+ "/#{api_version}/projects/#{project_id}/events/#{URI.escape(event_collection)}"
103
+ end
104
+
105
+ def check_event_data!(event_collection, properties)
106
+ raise ArgumentError, "Event collection can not be nil" unless event_collection
107
+ raise ArgumentError, "Event properties can not be nil" unless properties
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,199 @@
1
+ module Keen
2
+ class Client
3
+ module QueryingMethods
4
+
5
+ # Runs a count query.
6
+ # See detailed documentation here:
7
+ # https://keen.io/docs/api/reference/#count-resource
8
+ #
9
+ # @param event_collection
10
+ # @param params [Hash] (optional)
11
+ # group_by (optional)
12
+ # timeframe (optional)
13
+ # interval (optional)
14
+ # filters (optional) [Array]
15
+ # timezone (optional)
16
+ def count(event_collection, params={})
17
+ query(__method__, event_collection, params)
18
+ end
19
+
20
+ # Runs a count unique query.
21
+ # See detailed documentation here:
22
+ # https://keen.io/docs/api/reference/#count-unique-resource
23
+ #
24
+ # @param event_collection
25
+ # @param params [Hash] (optional)
26
+ # target_property (required)
27
+ # group_by (optional)
28
+ # timeframe (optional)
29
+ # interval (optional)
30
+ # filters (optional) [Array]
31
+ # timezone (optional)
32
+ def count_unique(event_collection, params)
33
+ query(__method__, event_collection, params)
34
+ end
35
+
36
+ # Runs a minimum query.
37
+ # See detailed documentation here:
38
+ # https://keen.io/docs/api/reference/#minimum-resource
39
+ #
40
+ # @param event_collection
41
+ # @param params [Hash] (optional)
42
+ # target_property (required)
43
+ # group_by (optional)
44
+ # timeframe (optional)
45
+ # interval (optional)
46
+ # filters (optional) [Array]
47
+ # timezone (optional)
48
+ def minimum(event_collection, params)
49
+ query(__method__, event_collection, params)
50
+ end
51
+
52
+ # Runs a maximum query.
53
+ # See detailed documentation here:
54
+ # https://keen.io/docs/api/reference/#maximum-resource
55
+ #
56
+ # @param event_collection
57
+ # @param params [Hash] (optional)
58
+ # target_property (required)
59
+ # group_by (optional)
60
+ # timeframe (optional)
61
+ # interval (optional)
62
+ # filters (optional) [Array]
63
+ # timezone (optional)
64
+ def maximum(event_collection, params)
65
+ query(__method__, event_collection, params)
66
+ end
67
+
68
+ # Runs a sum query.
69
+ # See detailed documentation here:
70
+ # https://keen.io/docs/api/reference/#sum-resource
71
+ #
72
+ # @param event_collection
73
+ # @param params [Hash] (optional)
74
+ # target_property (required)
75
+ # group_by (optional)
76
+ # timeframe (optional)
77
+ # interval (optional)
78
+ # filters (optional) [Array]
79
+ # timezone (optional)
80
+ def sum(event_collection, params)
81
+ query(__method__, event_collection, params)
82
+ end
83
+
84
+ # Runs a average query.
85
+ # See detailed documentation here:
86
+ # https://keen.io/docs/api/reference/#average-resource
87
+ #
88
+ # @param event_collection
89
+ # @param params [Hash] (optional)
90
+ # target_property (required)
91
+ # group_by (optional)
92
+ # timeframe (optional)
93
+ # interval (optional)
94
+ # filters (optional) [Array]
95
+ # timezone (optional)
96
+ def average(event_collection, params)
97
+ query(__method__, event_collection, params)
98
+ end
99
+
100
+ # Runs a select_unique query.
101
+ # See detailed documentation here:
102
+ # https://keen.io/docs/api/reference/#select-unique-resource
103
+ #
104
+ # @param event_collection
105
+ # @param params [Hash] (optional)
106
+ # target_property (required)
107
+ # group_by (optional)
108
+ # timeframe (optional)
109
+ # interval (optional)
110
+ # filters (optional) [Array]
111
+ # timezone (optional)
112
+ def select_unique(event_collection, params)
113
+ query(__method__, event_collection, params)
114
+ end
115
+
116
+ # Runs a extraction query.
117
+ # See detailed documentation here:
118
+ # https://keen.io/docs/api/reference/#extraction-resource
119
+ #
120
+ # @param event_collection
121
+ # @param params [Hash] (optional)
122
+ # target_property (required)
123
+ # group_by (optional)
124
+ # timeframe (optional)
125
+ # interval (optional)
126
+ # filters (optional) [Array]
127
+ # timezone (optional)
128
+ # latest (optional)
129
+ def extraction(event_collection, params={})
130
+ query(__method__, event_collection, params)
131
+ end
132
+
133
+ # Runs a funnel query.
134
+ # See detailed documentation here:
135
+ # https://keen.io/docs/api/reference/#funnel-resource
136
+ #
137
+ # @param event_collection
138
+ # @param params [Hash] (optional)
139
+ # steps (required)
140
+ def funnel(params)
141
+ query(__method__, nil, params)
142
+ end
143
+
144
+ private
145
+
146
+ def query(query_name, event_collection, params)
147
+ ensure_project_id!
148
+ ensure_api_key!
149
+
150
+ params[:api_key] = self.api_key
151
+
152
+ if event_collection
153
+ params[:event_collection] = event_collection
154
+ end
155
+
156
+ query_params = preprocess_params(params)
157
+
158
+ begin
159
+ response = Keen::HTTP::Sync.new(
160
+ api_host, api_port, api_sync_http_options).get(
161
+ :path => "#{api_query_resource_path(query_name)}?#{query_params}",
162
+ :headers => api_headers("sync"))
163
+ rescue Exception => http_error
164
+ raise HttpError.new("Couldn't perform #{query_name} on Keen IO: #{http_error.message}", http_error)
165
+ end
166
+
167
+ response_body = response.body.chomp
168
+ process_response(response.code, response_body)["result"]
169
+ end
170
+
171
+ def preprocess_params(params)
172
+ if params.key?(:filters)
173
+ params[:filters] = MultiJson.encode(params[:filters])
174
+ end
175
+
176
+ if params.key?(:steps)
177
+ params[:steps] = MultiJson.encode(params[:steps])
178
+ end
179
+
180
+ if params.key?(:timeframe) && params[:timeframe].is_a?(Hash)
181
+ params[:timeframe] = MultiJson.encode(params[:timeframe])
182
+ end
183
+
184
+ query_params = ""
185
+ params.each do |param, value|
186
+ query_params << "#{param}=#{URI.escape(value)}&"
187
+ end
188
+
189
+ query_params.chop!
190
+ query_params
191
+ end
192
+
193
+ def api_query_resource_path(analysis_type)
194
+ "/#{self.api_version}/projects/#{self.project_id}/queries/#{analysis_type}"
195
+ end
196
+ end
197
+ end
198
+ end
199
+