diff --git a/lib/index.js b/lib/index.js index fdb756b..1316f0c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -53,6 +53,9 @@ GA.prototype.initialize = function() { GA.prototype.track = proxy('track'); GA.prototype.page = proxy('page'); GA.prototype.screen = proxy('screen'); +GA.prototype.identify = proxy('identify'); +GA.prototype.group = proxy('group'); + /** * Proxy the method to classic or universal analytics. diff --git a/lib/universal/index.js b/lib/universal/index.js index 69ae258..6d389fe 100644 --- a/lib/universal/index.js +++ b/lib/universal/index.js @@ -114,6 +114,48 @@ GA.prototype.screen = function(screen, callback) { .end(this.handle(callback)); }; +/** + * Identify. + * + * @param {Screen} identify + * @param {Function} callback + */ + +GA.prototype.identify = function(identify, callback) { + var payload = mapper.identify(identify, this.settings); + if (payload) { + this + .post() + .type('form') + .send(payload) + .end(this.handle(callback)); + } else { + this.debug('no mapped traits'); + callback(); + } +}; + +/** + * Group. + * + * @param {Screen} group + * @param {Function} callback + */ + +GA.prototype.group = function(group, callback) { + var payload = mapper.group(group, this.settings); + if (payload) { + this + .post() + .type('form') + .send(payload) + .end(this.handle(callback)); + } else { + this.debug('no mapped traits'); + callback(); + } +}; + /** * Get headers. * diff --git a/lib/universal/mapper.js b/lib/universal/mapper.js index 69e3e26..323f63f 100644 --- a/lib/universal/mapper.js +++ b/lib/universal/mapper.js @@ -6,6 +6,7 @@ var Track = require('segmentio-facade').Track; var extend = require('@ndhoule/extend'); +var each = require('@ndhoule/each'); var fmt = require('util').format; var foldl = require('@ndhoule/foldl'); var hash = require('string-hash'); @@ -54,6 +55,62 @@ exports.screen = function(screen, settings) { return result; }; +/** + * Map Identify. + * + * https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#screenView + * + * @api public + * @param {identify} screen + * @param {Object} settings + * @return {Object} + */ + +exports.identify = function(identify, settings) { + var result = createCommonGAForm(identify, settings); + + // return undefined if there were no mapped traits present + var send; + each(function(val, key) { + if (result[shorten(val)]) send = true; + }, extend(settings.dimensions, settings.metrics)); + if (!send) return; + + result.t = 'event'; + result.el = 'identify'; + result.ea = 'identify' + result.ec = 'user'; + return result; +}; + +/** + * Map Group. + * + * https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#screenView + * + * @api public + * @param {identify} screen + * @param {Object} settings + * @return {Object} + */ + +exports.group = function(group, settings) { + var result = createCommonGAForm(group, settings); + + // return undefined if there were no mapped traits present + var send; + each(function(val, key) { + if (result[shorten(val)]) send = true; + }, extend(settings.dimensions, settings.metrics)); + if (!send) return; + + result.t = 'event'; + result.el = 'group'; + result.ea = 'group' + result.ec = 'user'; + return result; +}; + /** * Map Track. * diff --git a/package.json b/package.json index 2850a11..ab03bd3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test": "make test" }, "dependencies": { + "@ndhoule/each": "^2.0.1", "@ndhoule/extend": "^1.0.1", "@ndhoule/foldl": "^1.0.3", "@ndhoule/pick": "^1.0.2", @@ -22,6 +23,7 @@ "obj-case": "^0.1.1", "segmentio-facade": "^2.1.0", "segmentio-integration": "^3.2.0", + "sinon": "^1.17.4", "string-hash": "^1.1.0", "unix-time": "^1.0.1" }, diff --git a/test/fixtures/group-cm-cd.json b/test/fixtures/group-cm-cd.json new file mode 100644 index 0000000..4741e7d --- /dev/null +++ b/test/fixtures/group-cm-cd.json @@ -0,0 +1,38 @@ +{ + "settings": { + "dimensions": { + "connectedness": "dimension55", + "qualified": "dimension56", + "role": "dimension8", + "dontsend": "dimension1" + }, + "metrics": { + "net_new_integrations_trailing_30": "metric90" + }, + "sendUserId": true + }, + "input": { + "type": "group", + "userId": "user-id", + "groupId": "group-id", + "traits": { + "connectedness": "very", + "qualified": true, + "net_new_integrations_trailing_30": 4 + }, + "context": {} + }, + "output": { + "cid": 2710159508, + "tid": "UA-27033709-11", + "uid": "user-id", + "cd55": "very", + "cd56": "true", + "cm90": 4, + "ea":"group", + "el":"group", + "ec":"user", + "t": "event", + "v": 1 + } +} diff --git a/test/fixtures/identify-cm-cd.json b/test/fixtures/identify-cm-cd.json new file mode 100644 index 0000000..ec26079 --- /dev/null +++ b/test/fixtures/identify-cm-cd.json @@ -0,0 +1,37 @@ +{ + "settings": { + "dimensions": { + "connectedness": "dimension55", + "qualified": "dimension56", + "role": "dimension8", + "dontsend": "dimension1" + }, + "metrics": { + "plan": "metric90" + }, + "sendUserId": true + }, + "input": { + "type": "identify", + "userId": "user-id", + "traits": { + "connectedness": "very", + "qualified": true, + "role": "Product" + }, + "context": {} + }, + "output": { + "cid": 2710159508, + "tid": "UA-27033709-11", + "uid": "user-id", + "cd55": "very", + "cd56": "true", + "cd8": "Product", + "ea":"identify", + "el":"identify", + "ec":"user", + "t": "event", + "v": 1 + } +} diff --git a/test/universal.js b/test/universal.js index 94e9786..e1ef6d8 100644 --- a/test/universal.js +++ b/test/universal.js @@ -13,6 +13,7 @@ var fmt = require('util').format; var helpers = require('./helpers'); var requestOverride = require('./request-override'); var mapper = require('../lib/universal/mapper'); +var sinon = require('sinon'); /** * Tests. @@ -22,13 +23,16 @@ describe('Google Analytics :: Universal', function() { var ga; var settings; var test; + var sandbox; beforeEach(function() { + sandbox = sinon.sandbox.create(); settings = { serversideTrackingId: 'UA-27033709-11', mobileTrackingId: 'UA-27033709-23', serversideClassic: false }; + ga = new GoogleAnalytics(settings); test = new Test(ga.universal, __dirname); test.mapper(mapper); @@ -123,6 +127,78 @@ describe('Google Analytics :: Universal', function() { test.maps('screen-server-id', settings, options); }); }); + + describe('identify', function() { + var options = {'ignored': ['qt']}; + + it('should send traits that are mapped as custom dimensions and metrics', function() { + test.maps('identify-cm-cd', settings, options); + }); + + it('should get a good response from the API', function(done) { + var spy = sandbox.spy(ga.universal, 'post'); + var identify = {}; + settings.dimensions = { trait: "dimension8" }; + identify.userId = 'userId'; + identify.traits = { trait: true }; + test + .identify(identify, settings) + .expects(200) + .end(function(err, res){ + assert(spy.called, 'Expected spy to have been called'); + done(err); + });; + }); + + it('should skip if no traits are mapped', function(done){ + var spy = sandbox.spy(ga.universal, 'post'); + var identify = {}; + identify.userId = 'userId'; + identify.traits = {}; + test + .identify(identify, settings) + .end(function(err, res){ + assert(!spy.called, 'Expected spy to have not been called'); + done(err); + }); + }); + }); + + describe('group', function() { + var options = {'ignored': ['qt']}; + + it('should send account traits that are mapped as custom dimensions and metrics', function() { + test.maps('group-cm-cd', settings, options); + }); + + it('should get a good response from the API', function(done) { + var spy = sandbox.spy(ga.universal, 'post'); + var group = {}; + settings.dimensions = { trait: "dimension8" }; + group.userId = 'userId'; + group.traits = { trait: true }; + test + .group(group, settings) + .expects(200) + .end(function(err, res){ + assert(spy.called, 'Expected spy to have been called'); + done(err); + });; + }); + + it('should skip if no traits are mapped', function(done){ + var spy = sandbox.spy(ga.universal, 'post'); + var group = {}; + group.userId = 'userId'; + group.traits = {}; + test + .group(group, settings) + .end(function(err, res){ + assert(!spy.called, 'Expected spy to have not been called'); + done(err); + }); + }); + }); }); describe('.track()', function() {