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.
@@ -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
+