Skip to content

Commit 52137bd

Browse files
author
Dustin Larimer
committed
Merge pull request #16 from keenlabs/feature/queries
Query support!
2 parents b653008 + 1d0661b commit 52137bd

File tree

6 files changed

+658
-14
lines changed

6 files changed

+658
-14
lines changed

README.md

Lines changed: 135 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ Use npm to install!
1717
### Initialization
1818

1919
```javascript
20-
var keen = require('keen.io');
20+
var Keen = require('keen.io');
2121

2222
// Configure instance. Only projectId and writeKey are required to send data.
23-
var keen = keen.configure({
23+
var keen = Keen.configure({
2424
projectId: "<project_id>",
2525
writeKey: "<write_key>",
2626
readKey: "<read_key>",
@@ -31,20 +31,20 @@ var keen = keen.configure({
3131
You can also have multiple instances if you are connecting to multiple KeenIO accounts in the one project (probably edge case).
3232

3333
```javascript
34-
var keen = require('keen.io');
34+
var Keen = require('keen.io');
3535

3636
// Configure instance with API Key
37-
var keen1 = keen.configure({...});
38-
var keen2 = keen.configure({...});
37+
var keen1 = Keen.configure({...});
38+
var keen2 = Keen.configure({...});
3939
```
4040

4141
In the future there will be the ability to pass options into the initialisation such as batching inserts, etc. The structure of this hasn't been defined yet but will look something like the following.
4242

4343
```javascript
44-
var keen = require('keen.io');
44+
var Keen = require('keen.io');
4545

4646
// Configure instance with API Key and options
47-
var keen = keen.configure({
47+
var keen = Keen.configure({
4848
projectId: "<project_id>",
4949
batchEventInserts: 30
5050
});
@@ -53,8 +53,8 @@ var keen = keen.configure({
5353
### Send Events
5454

5555
```javascript
56-
var keen = require("keen.io");
57-
var keen = keen.configure({
56+
var Keen = require("keen.io");
57+
var keen = Keen.configure({
5858
projectId: "<project_id>",
5959
writeKey: "<write_key>"
6060
});
@@ -82,26 +82,148 @@ keen.addEvents({
8282
```
8383

8484
### Generate Scoped Key
85+
8586
```javascript
86-
var keen = require("keen.io");
87+
var Keen = require("keen.io");
8788
var apiKey = "YOUR_API_KEY";
88-
var scopedKey = keen.encryptScopedKey(apiKey, {
89+
var scopedKey = Keen.encryptScopedKey(apiKey, {
8990
"allowed_operations": ["read"],
9091
"filters": [{
9192
"property_name": "account.id",
9293
"operator": "eq",
9394
"property_value": "123"
9495
}]
9596
});
96-
var keen = keen.configure({
97+
var keen = Keen.configure({
9798
projectId: "<project_id>";
9899
readKey: scopedKey
99100
});
100101
```
101102

103+
## Queries
104+
105+
Analyses are first-class citizens, complete with parameter getters and setters.
106+
107+
The `<Client>.run` method is available on each configured client instance to run one or many analyses on a given project. Read more about running multiple analyses below.
108+
109+
**Format:**
110+
111+
```
112+
var your_analysis = new Keen.Query(analysisType, params);
113+
```
114+
115+
### Example Usage
116+
117+
```
118+
var Keen = require('keen.io');
119+
var keen = Keen.configure({
120+
projectId: "your_project_id",
121+
readKey: "your_read_key"
122+
});
123+
124+
var count = new Keen.Query("count", {
125+
event_collection: "pageviews",
126+
group_by: "property",
127+
timeframe: "this_7_days"
128+
});
129+
130+
// Send query
131+
keen.run(count, function(err, response){
132+
if (err) return console.log(err);
133+
// response.result
134+
});
135+
```
136+
137+
138+
### Query Analysis Types
139+
140+
All of the following analyses require an `event_collection` parameter. Some analyses have additional requirements, which are noted below.
141+
142+
`count`
143+
144+
`count_unique`
145+
146+
`sum` requires a `target_property` parameter, where value is an integer
147+
148+
`average` requires a `target_property` parameter, where value is an integer
149+
150+
`maximum` requires a `target_property` parameter, where value is an integer
151+
152+
`minimum` requires a `target_property` parameter, where value is an integer
153+
154+
`select_unique` requires a `target_property` parameter
155+
156+
`extraction`
157+
158+
**A note about extractions:** supply an optional `email` attribute to be notified when your extraction is ready for download. If email is not specified, your extraction will be processed synchronously and your data will be returned as JSON.
159+
160+
`Keen.Funnel` requires a `steps` attribute
161+
162+
**A note about funnels:** funnels require a `steps` as an array of objects. Each step requires an `event_collection` and `actor_property` parameter.
163+
164+
```
165+
var funfunfunnel = new Keen.Query('funnel', {
166+
steps: [
167+
{
168+
event_collection: "view_landing_page",
169+
actor_property: "user.id"
170+
},
171+
{
172+
event_collection: "signed_up",
173+
actor_property: "user.id"
174+
},
175+
],
176+
timeframe: "this_6_months"
177+
});
178+
```
179+
180+
181+
Learn more about funnels in the [API reference](https://keen.io/docs/data-analysis/funnels/#steps)
182+
183+
### Run multiple analyses at once
184+
185+
The `<Client>.run` method accepts an individual analysis or array of analyses. In the latter scenario, the callback is fired once all requests have completed without error. Query results are then returned in a correctly sequenced array.
186+
187+
Query results are also attached to the query object itself, and can be referenced as `this.data`.
188+
189+
```
190+
var avg_revenue = new Keen.Query("average", {
191+
event_collection: "purchase",
192+
target_property: "price",
193+
group_by: "geo.country"
194+
});
195+
var max_revenue = new Keen.Query("maximum", {
196+
event_collection: "purchase",
197+
target_property: "price",
198+
group_by: "geo.country"
199+
});
200+
201+
var mashup = keen.run([avg_revenue, max_revenue], function(err, res){
202+
if (err) return console.log(err);
203+
// res[0].result or this.data[0] (avg_revenue)
204+
// res[1].result or this.data[1] (max_revenue)
205+
});
206+
```
207+
208+
209+
### Get/Set Parameters and Refresh Queries
210+
211+
```
212+
// Based on previous example
213+
214+
// Update parameters
215+
avg_revenue.set({ timeframe: "this_21_days" });
216+
max_revenue.set({ timeframe: "this_21_days" });
217+
218+
// Re-run the query
219+
mashup.refresh();
220+
```
221+
222+
223+
102224
## Future Updates
103225

104-
Future module updates are planned to introduce the remaining API calls. You can see some of the spec for that in [examples/queries.js](https://github.com/keenlabs/KeenClient-Node/blob/master/examples/queries.js). Also, as mentioned above, specifying options when creating an instance to configure the behaviour of the instance (ie, batching event submissions).
226+
Future module updates are planned to introduce the remaining API calls. You can see some sketches for these in the [examples directory](https://github.com/keenlabs/KeenClient-Node/blob/master/examples/). Also, as mentioned above, specifying options when creating an instance to configure the behaviour of the instance (ie, batching event submissions).
105227

106228
## Contributing
107229

index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ var _ = require('underscore');
33
var crypto = require('crypto');
44
var qs = require('querystring');
55

6+
var KeenRequests = require('./lib/requests');
7+
var KeenQuery = require('./lib/query');
8+
69
function KeenApi(config) {
710
if (!config) {
811
throw new Error("The 'config' parameter must be specified and must be a JS object.");
@@ -188,6 +191,8 @@ function KeenApi(config) {
188191
request.get(self.readKey, path, params, callback);
189192
}
190193
};
194+
195+
this.run = KeenQuery.client.run;
191196
}
192197

193198
function configure(config) {
@@ -219,5 +224,6 @@ function decryptScopedKey(apiKey, scopedKey) {
219224
module.exports = {
220225
configure: configure,
221226
encryptScopedKey: encryptScopedKey,
222-
decryptScopedKey: decryptScopedKey
227+
decryptScopedKey: decryptScopedKey,
228+
Query: KeenQuery.Query
223229
};

lib/query.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
var _ = require('underscore');
2+
var KeenRequests = require('./requests');
3+
4+
/*!
5+
* -----------------
6+
* Keen IO Query JS
7+
* -----------------
8+
*/
9+
10+
var Keen = {};
11+
12+
// ------------------------------
13+
// Keen.Request
14+
// ------------------------------
15+
16+
Keen.Request = function(){
17+
this.data = {};
18+
this.configure.apply(this, arguments);
19+
}
20+
21+
Keen.Request.prototype.configure = function(client, queries, callback){
22+
this.client = client;
23+
this.queries = queries;
24+
this.callback = callback;
25+
this.run();
26+
return this;
27+
};
28+
29+
Keen.Request.prototype.run = function(){
30+
var self = this,
31+
completions = 0,
32+
response = [];
33+
34+
var handleResponse = function(err, res){
35+
if (err && self.callback) {
36+
return self.callback(err, null);
37+
}
38+
response[arguments[2]] = res, completions++;
39+
if (completions == self.queries.length) {
40+
self.data = (self.queries.length == 1) ? response[0] : response;
41+
if (self.callback) self.callback(null, self.data);
42+
}
43+
};
44+
45+
_.each(self.queries, function(query, index){
46+
var data, path = '/projects/' + self.client.projectId;
47+
var callbackSequencer = function(err, res){
48+
handleResponse(err, res, index);
49+
};
50+
51+
if (query instanceof Keen.Query) {
52+
path += query.path;
53+
data = query.params || {};
54+
}
55+
/* TODO: Test and deploy this
56+
else if (_.isString(query)) {
57+
path += '/saved_queries/' + query + '/result';
58+
data = { api_key: self.client.readKey };
59+
}*/
60+
else {
61+
throw new Error('Query #' + (index+1) +' is not valid');
62+
63+
}
64+
65+
KeenRequests.get.call(self.client, self.client.readKey, path, data, callbackSequencer);
66+
});
67+
68+
return self;
69+
};
70+
71+
72+
// ------------------------------
73+
// Keen.Query
74+
// ------------------------------
75+
76+
Keen.Query = function(){
77+
this.configure.apply(this, arguments);
78+
};
79+
80+
Keen.Query.prototype.configure = function(analysisType, params){
81+
//if (!collection) throw new Error('Event Collection name is required');
82+
var self = this;
83+
self.path = '/queries/' + analysisType;
84+
self.params = {};
85+
self.set(params);
86+
return self;
87+
};
88+
89+
Keen.Query.prototype.get = function(attribute) {
90+
if (this.params) {
91+
return this.params[attribute] || null;
92+
}
93+
};
94+
95+
Keen.Query.prototype.set = function(attributes) {
96+
var self = this;
97+
_.each(attributes, function(v, k){
98+
var key = k, value = v;
99+
if (k.match(new RegExp("[A-Z]"))) {
100+
key = k.replace(/([A-Z])/g, function($1) { return "_"+$1.toLowerCase(); });
101+
}
102+
self.params[key] = value;
103+
104+
if (_.isArray(value)) {
105+
_.each(value, function(dv, index){
106+
if (_.isObject(dv)) {
107+
_.each(dv, function(deepValue, deepKey){
108+
if (deepKey.match(new RegExp("[A-Z]"))) {
109+
var _deepKey = deepKey.replace(/([A-Z])/g, function($1) { return "_"+$1.toLowerCase(); });
110+
delete self.params[key][index][deepKey];
111+
self.params[key][index][_deepKey] = deepValue;
112+
}
113+
});
114+
}
115+
});
116+
}
117+
118+
});
119+
return self;
120+
};
121+
122+
123+
// Export Methods
124+
// ------------------------------
125+
module.exports = {
126+
client: {
127+
run: function(query, callback){
128+
if (!query) throw new Error('At least one query is required');
129+
var queries = (_.isArray(query)) ? query : [query];
130+
return new Keen.Request(this, queries, callback);
131+
}
132+
},
133+
Query: Keen.Query
134+
};

0 commit comments

Comments
 (0)