Comparing version
40
index.js
@@ -6,2 +6,5 @@ var rest = require('superagent'); | ||
var KeenRequests = require('./lib/requests'); | ||
var KeenQuery = require('./lib/query'); | ||
function KeenApi(config) { | ||
@@ -24,8 +27,9 @@ if (!config) { | ||
var apiVersion = this.apiVersion; | ||
var self = this; | ||
var request = { | ||
get: function(apiKey, path, callback) { | ||
get: function(apiKey, path, data, callback) { | ||
rest | ||
.get(baseUrl + apiVersion + path) | ||
.set('Authorization', apiKey) | ||
.send(data || {}) | ||
.end(function(err, res) { | ||
@@ -75,6 +79,6 @@ processResponse(err, res, callback); | ||
list: function(callback) { | ||
request.get(this.masterKey, '/projects', callback); | ||
request.get(self.masterKey, '/projects', null, callback); | ||
}, | ||
view: function(projectId, callback) { | ||
request.get(this.masterKey, '/projects/' + projectId, callback); | ||
request.get(self.masterKey, '/projects/' + projectId, callback); | ||
} | ||
@@ -85,3 +89,3 @@ }; | ||
list: function(projectId, callback) { | ||
request.get(this.masterKey, '/projects/' + projectId + '/events', callback); | ||
request.get(self.masterKey, '/projects/' + projectId + '/events', callback); | ||
}, | ||
@@ -102,3 +106,3 @@ insert: function(projectId, events, callback) { | ||
}); | ||
request.post(this.writeKey, '/projects/' + projectId + '/events', data, callback); | ||
request.post(self.writeKey, '/projects/' + projectId + '/events', data, callback); | ||
} | ||
@@ -109,6 +113,6 @@ }; | ||
view: function(projectId, collection, property, callback) { | ||
request.get(this.masterKey, '/projects/' + projectId + '/events/' + collection + '/properties/' + property, callback); | ||
request.get(self.masterKey, '/projects/' + projectId + '/events/' + collection + '/properties/' + property, callback); | ||
}, | ||
remove: function(projectId, collection, property, callback) { | ||
request.del(this.masterKey, '/projects/' + projectId + '/events/' + collection + '/properties/' + property, callback); | ||
request.del(self.masterKey, '/projects/' + projectId + '/events/' + collection + '/properties/' + property, callback); | ||
} | ||
@@ -119,6 +123,6 @@ }; | ||
view: function(projectId, collection, callback) { | ||
request.get(this.masterKey, '/projects/' + projectId + '/events/' + collection, callback); | ||
request.get(self.masterKey, '/projects/' + projectId + '/events/' + collection, null, callback); | ||
}, | ||
remove: function(projectId, collection, callback) { | ||
request.del(this.masterKey, '/projects/' + projectId + '/events/' + collection, callback); | ||
request.del(self.masterKey, '/projects/' + projectId + '/events/' + collection, callback); | ||
} | ||
@@ -169,4 +173,4 @@ }; | ||
if(method === 'post') { | ||
return request.post(this[keyType], path, params, callback); | ||
if(method === 'post' || method === 'get') { | ||
return request[method](this[keyType], path, params, callback); | ||
} | ||
@@ -191,2 +195,11 @@ | ||
}; | ||
this.queries = { | ||
extraction: function(projectId, collection, params, callback){ | ||
var path = '/projects/' + projectId + '/queries/extraction?event_collection=' + collection; | ||
request.get(self.readKey, path, params, callback); | ||
} | ||
}; | ||
this.run = KeenQuery.client.run; | ||
} | ||
@@ -223,3 +236,4 @@ | ||
encryptScopedKey: encryptScopedKey, | ||
decryptScopedKey: decryptScopedKey | ||
decryptScopedKey: decryptScopedKey, | ||
Query: KeenQuery.Query | ||
}; |
{ | ||
"name": "keen.io", | ||
"version": "0.0.4", | ||
"version": "0.1.1", | ||
"description": "Keen IO NodeJS module. Keen IO is a hosted API to collect, analyze, and visualize your data.", | ||
@@ -23,2 +23,17 @@ "homepage": "https://github.com/keenlabs/KeenClient-Node", | ||
"url": "http://keen.io" | ||
}, | ||
{ | ||
"name": "Dustin Larimer", | ||
"email": "dustin@keen.io", | ||
"url": "https://github.com/dustinlarimer" | ||
}, | ||
{ | ||
"name": "Bogdan Cirlig", | ||
"email": "", | ||
"url": "https://github.com/bibanul" | ||
}, | ||
{ | ||
"name": "Jah Raphael", | ||
"email": "jahraphael@yahoo.com", | ||
"url": "https://github.com/jahraphael" | ||
} | ||
@@ -25,0 +40,0 @@ ], |
150
README.md
# Keen IO - NodeJS | ||
[](https://travis-ci.org/keenlabs/KeenClient-Node) | ||
[](https://travis-ci.org/keenlabs/KeenClient-Node) | ||
@@ -20,6 +20,6 @@ Keen IO is an online service to collect, analyze, and visualize your data. | ||
```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>", | ||
@@ -35,7 +35,7 @@ writeKey: "<write_key>", | ||
```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({...}); | ||
``` | ||
@@ -46,6 +46,6 @@ | ||
```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>", | ||
@@ -59,4 +59,4 @@ batchEventInserts: 30 | ||
```javascript | ||
var keen = require("keen.io"); | ||
var keen = keen.configure({ | ||
var Keen = require("keen.io"); | ||
var keen = Keen.configure({ | ||
projectId: "<project_id>", | ||
@@ -89,6 +89,7 @@ writeKey: "<write_key>" | ||
### 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"], | ||
@@ -101,3 +102,3 @@ "filters": [{ | ||
}); | ||
var keen = keen.configure({ | ||
var keen = Keen.configure({ | ||
projectId: "<project_id>"; | ||
@@ -108,5 +109,126 @@ 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). | ||
@@ -113,0 +235,0 @@ ## Contributing |
314
test/test.js
/* jshint quotmark:false,indent:4,maxlen:600 */ | ||
var should = require("should"); | ||
var _ = require('underscore'); | ||
var buildQueryString = function(params){ | ||
var query = []; | ||
for (var key in params) { | ||
if (params[key]) { | ||
var value = params[key]; | ||
if (Object.prototype.toString.call(value) !== '[object String]') { | ||
value = JSON.stringify(value); | ||
} | ||
value = encodeURIComponent(value); | ||
query.push(key + '=' + value); | ||
} | ||
} | ||
return "?" + query.join('&'); | ||
}; | ||
describe("keen", function() { | ||
var keen; | ||
var Keen = require("../"); | ||
var projectId = "fakeProjectId"; | ||
var writeKey = "fakeWriteKey"; | ||
var readKey = "fakeReadKey"; | ||
var nock = require("nock"); | ||
@@ -20,2 +38,6 @@ | ||
afterEach(function() { | ||
nock.cleanAll(); | ||
}); | ||
it("configure should set up client correctly", function() { | ||
@@ -218,3 +240,3 @@ keen = require("../"); | ||
it('should send the request', function() { | ||
mockGetRequest("/3.0/projects/"+projectId+"/queries/count?event_collection=foo", 200, mockResponse); | ||
mockGetRequest("/"+ apiVersion +"/projects/"+ projectId +"/queries/count?event_collection=foo", 200, mockResponse); | ||
keen.request('get', 'read', '/queries/count', {event_collection:'foo'}, function(err, res) { | ||
@@ -227,4 +249,4 @@ (err === null).should.be.true; | ||
it('has optional params', function() { | ||
mockGetRequest("/3.0/projects/"+projectId+"/queries/count?event_collection=bar", 200, mockResponse); | ||
keen.request('get', 'read', '/queries/count?event_collection=bar', function(err, res) { | ||
mockGetRequest("/"+ apiVersion +"/projects/"+projectId+"/queries/count?event_collection=bar", 200, mockResponse); | ||
keen.request('get', 'read', '/queries/count', {event_collection:'bar'}, function(err, res) { | ||
(err === null).should.be.true; | ||
@@ -236,2 +258,288 @@ res.should.eql(mockResponse); | ||
}); | ||
describe('Queries', function() { | ||
beforeEach(function() { | ||
nock.cleanAll(); | ||
Keen = require("../"); | ||
keen = Keen.configure({ | ||
projectId: projectId, | ||
readKey: readKey | ||
}); | ||
}); | ||
describe('<Client>.run method', function(){ | ||
it('should be a method', function(){ | ||
keen['run'].should.be.a.Function; | ||
}); | ||
it('should throw an error when passed an invalid object', function(){ | ||
(function(){ | ||
keen.run(null); | ||
}).should.throwError(); | ||
(function(){ | ||
keen.run({}); | ||
}).should.throwError(); | ||
(function(){ | ||
keen.run(0); | ||
}).should.throwError(); | ||
// This should be removed when 'saved_query' support is validated | ||
(function(){ | ||
keen.run('string'); | ||
}).should.throwError(); | ||
}); | ||
}); | ||
describe('Analysis Types', function(){ | ||
var analyses = [ | ||
'count', | ||
'count_unique', | ||
'sum', | ||
'median', | ||
'percentile', | ||
'average', | ||
'minimum', | ||
'maximum', | ||
'select_unique', | ||
'extraction' | ||
]; | ||
_.each(analyses, function(type){ | ||
var analysis = new Keen.Query(type, { | ||
eventCollection: 'eventCollection', | ||
timeframe: 'this_7_days' | ||
}); | ||
var query_path = "/3.0/projects/" + projectId + "/queries/" + type; | ||
it('should be an instance of Keen.Query', function(){ | ||
analysis.should.be.an.instanceOf(Keen.Query); | ||
}); | ||
it('should have a correct path propery', function(){ | ||
analysis.should.have.property('path'); | ||
analysis.path.should.eql('/queries/' + type); | ||
}); | ||
it('should have a params property with supplied parameters', function(){ | ||
analysis.should.have.property('params'); | ||
analysis.params.should.have.property('event_collection', 'eventCollection'); | ||
analysis.params.should.have.property('timeframe', 'this_7_days'); | ||
}); | ||
it('should have a #get method that returns a requested parameter', function(){ | ||
analysis.get.should.be.a.Function; | ||
analysis.get('timeframe').should.eql('this_7_days'); | ||
}); | ||
it('should have a #set method that sets all supplied properties', function(){ | ||
analysis.set.should.be.a.Function; | ||
analysis.set({ group_by: 'property', target_property: 'referrer' }); | ||
analysis.params.should.have.property('group_by', 'property'); | ||
analysis.params.should.have.property('target_property', 'referrer'); | ||
}); | ||
it('should #set under_score attributes when camelCase attributes are supplied', function(){ | ||
analysis.set({ groupBy: 'property' }); | ||
analysis.params.should.have.property('group_by', 'property'); | ||
}); | ||
describe('When handled by <Client>.run method', function(){ | ||
beforeEach(function() { | ||
nock.cleanAll(); | ||
}); | ||
describe('Single analyses', function(){ | ||
it('should return a response when successful', function(done){ | ||
var mockResponse = { result: 1 }; | ||
mockGetRequest(query_path, 200, mockResponse); | ||
analysis.params = {}; | ||
var test = keen.run(analysis, function(err, res){ | ||
(err === null).should.be.true; | ||
res.should.eql(mockResponse); | ||
done(); | ||
}); | ||
}); | ||
it('should return an error when unsuccessful', function(done){ | ||
var mockResponse = { error_code: 'ResourceNotFoundError', message: 'no foo' }; | ||
mockGetRequest(query_path, 500, mockResponse); | ||
analysis.params = {}; | ||
var test = keen.run(analysis, function(err, res){ | ||
err.should.be.an.instanceOf(Error); | ||
err.should.have.property('code', mockResponse.error_code); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('Multiple analyses', function(){ | ||
it('should return a single response when successful', function(done){ | ||
var mockResponse = { result: 1 }; | ||
mockGetRequest(query_path, 200, mockResponse); | ||
mockGetRequest(query_path, 200, mockResponse); | ||
mockGetRequest(query_path, 200, mockResponse); | ||
analysis.params = {}; | ||
var test = keen.run([analysis, analysis, analysis], function(err, res){ | ||
(err === null).should.be.true; | ||
res.should.be.an.Array; | ||
res.should.have.length(3); | ||
res.should.eql([mockResponse, mockResponse, mockResponse]); | ||
done(); | ||
}); | ||
}); | ||
it('should return a single error when unsuccessful', function(done){ | ||
var mockResponse = { error_code: 'ResourceNotFoundError', message: 'no foo' }; | ||
mockGetRequest(query_path, 500, mockResponse); | ||
analysis.params = {}; | ||
var test = keen.run([analysis, analysis, analysis], function(err, res){ | ||
err.should.be.an.instanceOf(Error); | ||
err.should.have.property('code', mockResponse.error_code); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('Funnels', function(){ | ||
var funnel = new Keen.Query('funnel', { | ||
steps: [ | ||
{ event_collection: "view_landing_page", actor_property: "user.id" }, | ||
{ eventCollection: "sign_up", actorProperty: "user.id" } | ||
], | ||
timeframe: "this_21_days" | ||
}); | ||
var funnel_path = "/3.0/projects/" + projectId + "/queries/funnel" + buildQueryString(funnel.params); | ||
it('should be an instance of Keen.Query', function(){ | ||
funnel.should.be.an.instanceOf(Keen.Query); | ||
}); | ||
it('should have a correct path propery', function(){ | ||
funnel.should.have.property('path'); | ||
funnel.path.should.eql('/queries/funnel'); | ||
}); | ||
it('should have a params property with supplied parameters', function(){ | ||
funnel.should.have.property('params'); | ||
funnel.params.should.have.property('steps'); | ||
funnel.params.steps.should.be.an.Array.with.lengthOf(2); | ||
}); | ||
it('should have steps with parameters in proper case', function(){ | ||
funnel.params.steps[1].should.have.property('event_collection'); | ||
funnel.params.steps[1].should.have.property('actor_property'); | ||
}); | ||
it('should have a #get method that returns a requested parameter', function(){ | ||
funnel.get.should.be.a.Function; | ||
funnel.get('steps').should.be.an.Array; | ||
funnel.get('timeframe').should.eql('this_21_days'); | ||
}); | ||
it('should have a #set method that sets all supplied properties', function(){ | ||
funnel.set.should.be.a.Function; | ||
funnel.set({ timeframe: 'this_21_days' }); | ||
funnel.params.should.have.property('timeframe', 'this_21_days'); | ||
}); | ||
it('should #set under_score step attributes when camelCase are supplied ', function(){ | ||
funnel.set({ steps: [ | ||
{ eventCollection: "view_landing_page", actorProperty: "user.id" }, | ||
{ eventCollection: "sign_up", actorProperty: "user.id" } | ||
] }); | ||
funnel.params.steps[0].should.have.property('event_collection', 'view_landing_page'); | ||
funnel.params.steps[0].should.have.property('actor_property', 'user.id'); | ||
funnel.params.steps[1].should.have.property('event_collection', 'sign_up'); | ||
funnel.params.steps[1].should.have.property('actor_property', 'user.id'); | ||
}); | ||
describe('When handled by <Client>.run method', function(){ | ||
beforeEach(function() { | ||
nock.cleanAll(); | ||
}); | ||
describe('Single analyses', function(){ | ||
it('should return a response when successful', function(done){ | ||
var mockResponse = { result: 1 }; | ||
mockGetRequest(funnel_path, 200, mockResponse); | ||
var test = keen.run(funnel, function(err, res){ | ||
(err === null).should.be.true; | ||
res.should.eql(mockResponse); | ||
done(); | ||
}); | ||
}); | ||
it('should return an error when unsuccessful', function(done){ | ||
var mockResponse = { error_code: 'ResourceNotFoundError', message: 'no foo' }; | ||
mockGetRequest(funnel_path, 500, mockResponse); | ||
var test = keen.run(funnel, function(err, res){ | ||
err.should.be.an.instanceOf(Error); | ||
err.should.have.property('code', mockResponse.error_code); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('Multiple analyses', function(){ | ||
it('should return a single response when successful', function(done){ | ||
var mockResponse = { result: 1 }; | ||
mockGetRequest(funnel_path, 200, mockResponse); | ||
mockGetRequest(funnel_path, 200, mockResponse); | ||
mockGetRequest(funnel_path, 200, mockResponse); | ||
var test = keen.run([funnel, funnel, funnel], function(err, res){ | ||
(err === null).should.be.true; | ||
res.should.be.an.Array; | ||
res.should.have.length(3); | ||
res.should.eql([mockResponse, mockResponse, mockResponse]); | ||
done(); | ||
}); | ||
}); | ||
it('should return a single error when unsuccessful', function(done){ | ||
var mockResponse = { error_code: 'ResourceNotFoundError', message: 'no foo' }; | ||
mockGetRequest(funnel_path, 500, mockResponse); | ||
var test = keen.run([funnel, funnel, funnel], function(err, res){ | ||
err.should.be.an.instanceOf(Error); | ||
err.should.have.property('code', mockResponse.error_code); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
44119
84.37%15
15.38%936
84.25%266
84.72%1
Infinity%