Skip to content

Commit

Permalink
Merge pull request #16 from keenlabs/feature/queries
Browse files Browse the repository at this point in the history
Query support!
  • Loading branch information
Dustin Larimer committed Apr 14, 2014
2 parents b653008 + 1d0661b commit 52137bd
Show file tree
Hide file tree
Showing 6 changed files with 658 additions and 14 deletions.
148 changes: 135 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ Use npm to install!
### Initialization

```javascript
var keen = require('keen.io');
var Keen = require('keen.io');

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

```javascript
var keen = require('keen.io');
var Keen = require('keen.io');

// Configure instance with API Key
var keen1 = keen.configure({...});
var keen2 = keen.configure({...});
var keen1 = Keen.configure({...});
var keen2 = Keen.configure({...});
```

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.

```javascript
var keen = require('keen.io');
var Keen = require('keen.io');

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

```javascript
var keen = require("keen.io");
var keen = keen.configure({
var Keen = require("keen.io");
var keen = Keen.configure({
projectId: "<project_id>",
writeKey: "<write_key>"
});
Expand Down Expand Up @@ -82,26 +82,148 @@ keen.addEvents({
```

### Generate Scoped Key

```javascript
var keen = require("keen.io");
var Keen = require("keen.io");
var apiKey = "YOUR_API_KEY";
var scopedKey = keen.encryptScopedKey(apiKey, {
var scopedKey = Keen.encryptScopedKey(apiKey, {
"allowed_operations": ["read"],
"filters": [{
"property_name": "account.id",
"operator": "eq",
"property_value": "123"
}]
});
var keen = keen.configure({
var keen = Keen.configure({
projectId: "<project_id>";
readKey: scopedKey
});
```

## Queries

Analyses are first-class citizens, complete with parameter getters and setters.

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.

**Format:**

```
var your_analysis = new Keen.Query(analysisType, params);
```

### Example Usage

```
var Keen = require('keen.io');
var keen = Keen.configure({
projectId: "your_project_id",
readKey: "your_read_key"
});
var count = new Keen.Query("count", {
event_collection: "pageviews",
group_by: "property",
timeframe: "this_7_days"
});
// Send query
keen.run(count, function(err, response){
if (err) return console.log(err);
// response.result
});
```


### Query Analysis Types

All of the following analyses require an `event_collection` parameter. Some analyses have additional requirements, which are noted below.

`count`

`count_unique`

`sum` requires a `target_property` parameter, where value is an integer

`average` requires a `target_property` parameter, where value is an integer

`maximum` requires a `target_property` parameter, where value is an integer

`minimum` requires a `target_property` parameter, where value is an integer

`select_unique` requires a `target_property` parameter

`extraction`

**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.

`Keen.Funnel` requires a `steps` attribute

**A note about funnels:** funnels require a `steps` as an array of objects. Each step requires an `event_collection` and `actor_property` parameter.

```
var funfunfunnel = new Keen.Query('funnel', {
steps: [
{
event_collection: "view_landing_page",
actor_property: "user.id"
},
{
event_collection: "signed_up",
actor_property: "user.id"
},
],
timeframe: "this_6_months"
});
```


Learn more about funnels in the [API reference](https://keen.io/docs/data-analysis/funnels/#steps)

### Run multiple analyses at once

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.

Query results are also attached to the query object itself, and can be referenced as `this.data`.

```
var avg_revenue = new Keen.Query("average", {
event_collection: "purchase",
target_property: "price",
group_by: "geo.country"
});
var max_revenue = new Keen.Query("maximum", {
event_collection: "purchase",
target_property: "price",
group_by: "geo.country"
});
var mashup = keen.run([avg_revenue, max_revenue], function(err, res){
if (err) return console.log(err);
// res[0].result or this.data[0] (avg_revenue)
// res[1].result or this.data[1] (max_revenue)
});
```


### Get/Set Parameters and Refresh Queries

```
// Based on previous example
// Update parameters
avg_revenue.set({ timeframe: "this_21_days" });
max_revenue.set({ timeframe: "this_21_days" });
// Re-run the query
mashup.refresh();
```



## Future Updates

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).
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).

## Contributing

Expand Down
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ var _ = require('underscore');
var crypto = require('crypto');
var qs = require('querystring');

var KeenRequests = require('./lib/requests');
var KeenQuery = require('./lib/query');

function KeenApi(config) {
if (!config) {
throw new Error("The 'config' parameter must be specified and must be a JS object.");
Expand Down Expand Up @@ -188,6 +191,8 @@ function KeenApi(config) {
request.get(self.readKey, path, params, callback);
}
};

this.run = KeenQuery.client.run;
}

function configure(config) {
Expand Down Expand Up @@ -219,5 +224,6 @@ function decryptScopedKey(apiKey, scopedKey) {
module.exports = {
configure: configure,
encryptScopedKey: encryptScopedKey,
decryptScopedKey: decryptScopedKey
decryptScopedKey: decryptScopedKey,
Query: KeenQuery.Query
};
134 changes: 134 additions & 0 deletions lib/query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
var _ = require('underscore');
var KeenRequests = require('./requests');

/*!
* -----------------
* Keen IO Query JS
* -----------------
*/

var Keen = {};

// ------------------------------
// Keen.Request
// ------------------------------

Keen.Request = function(){
this.data = {};
this.configure.apply(this, arguments);
}

Keen.Request.prototype.configure = function(client, queries, callback){
this.client = client;
this.queries = queries;
this.callback = callback;
this.run();
return this;
};

Keen.Request.prototype.run = function(){
var self = this,
completions = 0,
response = [];

var handleResponse = function(err, res){
if (err && self.callback) {
return self.callback(err, null);
}
response[arguments[2]] = res, completions++;
if (completions == self.queries.length) {
self.data = (self.queries.length == 1) ? response[0] : response;
if (self.callback) self.callback(null, self.data);
}
};

_.each(self.queries, function(query, index){
var data, path = '/projects/' + self.client.projectId;
var callbackSequencer = function(err, res){
handleResponse(err, res, index);
};

if (query instanceof Keen.Query) {
path += query.path;
data = query.params || {};
}
/* TODO: Test and deploy this
else if (_.isString(query)) {
path += '/saved_queries/' + query + '/result';
data = { api_key: self.client.readKey };
}*/
else {
throw new Error('Query #' + (index+1) +' is not valid');

}

KeenRequests.get.call(self.client, self.client.readKey, path, data, callbackSequencer);
});

return self;
};


// ------------------------------
// Keen.Query
// ------------------------------

Keen.Query = function(){
this.configure.apply(this, arguments);
};

Keen.Query.prototype.configure = function(analysisType, params){
//if (!collection) throw new Error('Event Collection name is required');
var self = this;
self.path = '/queries/' + analysisType;
self.params = {};
self.set(params);
return self;
};

Keen.Query.prototype.get = function(attribute) {
if (this.params) {
return this.params[attribute] || null;
}
};

Keen.Query.prototype.set = function(attributes) {
var self = this;
_.each(attributes, function(v, k){
var key = k, value = v;
if (k.match(new RegExp("[A-Z]"))) {
key = k.replace(/([A-Z])/g, function($1) { return "_"+$1.toLowerCase(); });
}
self.params[key] = value;

if (_.isArray(value)) {
_.each(value, function(dv, index){
if (_.isObject(dv)) {
_.each(dv, function(deepValue, deepKey){
if (deepKey.match(new RegExp("[A-Z]"))) {
var _deepKey = deepKey.replace(/([A-Z])/g, function($1) { return "_"+$1.toLowerCase(); });
delete self.params[key][index][deepKey];
self.params[key][index][_deepKey] = deepValue;
}
});
}
});
}

});
return self;
};


// Export Methods
// ------------------------------
module.exports = {
client: {
run: function(query, callback){
if (!query) throw new Error('At least one query is required');
var queries = (_.isArray(query)) ? query : [query];
return new Keen.Request(this, queries, callback);
}
},
Query: Keen.Query
};
Loading

0 comments on commit 52137bd

Please sign in to comment.