Skip to content
This repository was archived by the owner on Apr 28, 2022. It is now read-only.

Commit a98bbbc

Browse files
author
Nathan Houle
committed
Merge pull request #440 from segmentio/feat/context.page
Add `.page` to all calls' contexts
2 parents 9e1d71c + 9b0f782 commit a98bbbc

File tree

4 files changed

+126
-58
lines changed

4 files changed

+126
-58
lines changed

component.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"ianstormtaylor/bind": "0.0.2",
2525
"ianstormtaylor/callback": "0.0.1",
2626
"ianstormtaylor/is": "0.1.0",
27+
"ndhoule/pick": "1.0.1",
28+
"matthewp/keys": "0.0.3",
2729
"segmentio/after": "0.0.1",
2830
"segmentio/analytics.js-integrations": "1.3.x",
2931
"segmentio/canonical": "0.0.1",
@@ -53,4 +55,4 @@
5355
"timoxley/next-tick": "*",
5456
"yields/gravy": "*"
5557
}
56-
}
58+
}

lib/analytics.js

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ var _analytics = window.analytics;
33
var after = require('after');
44
var bind = require('bind');
55
var callback = require('callback');
6-
var canonical = require('canonical');
76
var clone = require('clone');
87
var cookie = require('./cookie');
98
var debug = require('debug');
@@ -16,13 +15,14 @@ var isEmail = require('is-email');
1615
var isMeta = require('is-meta');
1716
var newDate = require('new-date');
1817
var on = require('event').bind;
18+
var pageDefaults = require('./pageDefaults');
19+
var pick = require('pick');
1920
var prevent = require('prevent');
2021
var querystring = require('querystring');
2122
var normalize = require('./normalize');
2223
var size = require('object').length;
2324
var keys = require('object').keys;
2425
var store = require('./store');
25-
var url = require('url');
2626
var user = require('./user');
2727
var Facade = require('facade');
2828
var Identify = Facade.Identify;
@@ -197,7 +197,6 @@ Analytics.prototype.identify = function (id, traits, options, fn) {
197197
if (is.fn(traits)) fn = traits, options = null, traits = null;
198198
if (is.object(id)) options = traits, traits = id, id = user.id();
199199

200-
201200
// clone traits before we manipulate so we don't do anything uncouth, and take
202201
// from `user` so that we carryover anonymous traits
203202
user.identify(id, traits);
@@ -405,19 +404,24 @@ Analytics.prototype.page = function (category, name, properties, options, fn) {
405404
if (is.object(name)) options = properties, properties = name, name = null;
406405
if (is.string(category) && !is.string(name)) name = category, category = null;
407406

408-
var defs = {
409-
path: canonicalPath(),
410-
referrer: document.referrer,
411-
title: document.title,
412-
search: location.search
413-
};
414-
415-
if (name) defs.name = name;
416-
if (category) defs.category = category;
417-
418407
properties = clone(properties) || {};
408+
if (category) properties.name = name;
409+
if (name) properties.category = category;
410+
411+
// Ensure properties has baseline spec properties.
412+
// TODO: Eventually move these entirely to `options.context.page`
413+
var defs = pageDefaults();
419414
defaults(properties, defs);
420-
properties.url = properties.url || canonicalUrl(properties.search);
415+
416+
// Mirror user overrides to `options.context.page` (but exclude custom properties)
417+
// (Any page defaults get applied in `this.normalize` for consistency.)
418+
// Weird, yeah--moving special props to `context.page` will fix this in the long term.
419+
var overrides = pick(keys(defs), properties);
420+
if (!is.empty(overrides)) {
421+
options = options || {};
422+
options.context = options.context || {};
423+
options.context.page = overrides;
424+
}
421425

422426
var msg = this.normalize({
423427
properties: properties,
@@ -620,6 +624,10 @@ Analytics.prototype.normalize = function(msg){
620624
msg = normalize(msg, keys(this._integrations));
621625
if (msg.anonymousId) user.anonymousId(msg.anonymousId);
622626
msg.anonymousId = user.anonymousId();
627+
628+
// Ensure all outgoing requests include page data in their contexts.
629+
msg.context.page = defaults(msg.context.page || {}, pageDefaults());
630+
623631
return msg;
624632
};
625633

@@ -632,31 +640,3 @@ Analytics.prototype.noConflict = function(){
632640
return this;
633641
};
634642

635-
/**
636-
* Return the canonical path for the page.
637-
*
638-
* @return {String}
639-
*/
640-
641-
function canonicalPath () {
642-
var canon = canonical();
643-
if (!canon) return window.location.pathname;
644-
var parsed = url.parse(canon);
645-
return parsed.pathname;
646-
}
647-
648-
/**
649-
* Return the canonical URL for the page concat the given `search`
650-
* and strip the hash.
651-
*
652-
* @param {String} search
653-
* @return {String}
654-
*/
655-
656-
function canonicalUrl (search) {
657-
var canon = canonical();
658-
if (canon) return ~canon.indexOf('?') ? canon : canon + search;
659-
var url = window.location.href;
660-
var i = url.indexOf('#');
661-
return -1 == i ? url : url.slice(0, i);
662-
}

lib/pageDefaults.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
/**
3+
* Module dependencies.
4+
*/
5+
6+
var canonical = require('canonical');
7+
var url = require('url');
8+
9+
/**
10+
* Return a default `options.context.page` object.
11+
*
12+
* https://segment.com/docs/spec/page/#properties
13+
*
14+
* @return {Object}
15+
*/
16+
17+
function pageDefaults() {
18+
return {
19+
path: canonicalPath(),
20+
referrer: document.referrer,
21+
search: location.search,
22+
title: document.title,
23+
url: canonicalUrl(location.search)
24+
};
25+
}
26+
27+
/**
28+
* Return the canonical path for the page.
29+
*
30+
* @return {String}
31+
*/
32+
33+
function canonicalPath () {
34+
var canon = canonical();
35+
if (!canon) return window.location.pathname;
36+
var parsed = url.parse(canon);
37+
return parsed.pathname;
38+
}
39+
40+
/**
41+
* Return the canonical URL for the page concat the given `search`
42+
* and strip the hash.
43+
*
44+
* @param {String} search
45+
* @return {String}
46+
*/
47+
48+
function canonicalUrl (search) {
49+
var canon = canonical();
50+
if (canon) return ~canon.indexOf('?') ? canon : canon + search;
51+
var url = window.location.href;
52+
var i = url.indexOf('#');
53+
return -1 === i ? url : url.slice(0, i);
54+
}
55+
56+
/**
57+
* Exports.
58+
*/
59+
60+
module.exports = pageDefaults;

test/analytics.js

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ describe('Analytics', function () {
88
var bind = require('event').bind;
99
var cookie = Analytics.cookie;
1010
var equal = require('equals');
11+
var extend = require('extend');
1112
var group = analytics.group();
1213
var is = require('is');
1314
var jQuery = require('jquery');
15+
var pageDefaults = require('../lib/pageDefaults');
1416
var sinon = require('sinon');
1517
var store = Analytics.store;
1618
var tick = require('next-tick');
@@ -24,6 +26,7 @@ describe('Analytics', function () {
2426
var Page = Facade.Page;
2527

2628
var analytics;
29+
var contextPage;
2730
var Test;
2831
var settings;
2932

@@ -33,6 +36,8 @@ describe('Analytics', function () {
3336
key: 'key'
3437
}
3538
};
39+
40+
contextPage = pageDefaults();
3641
});
3742

3843
beforeEach(function () {
@@ -406,17 +411,6 @@ describe('Analytics', function () {
406411
el.parentNode.removeChild(el);
407412
});
408413

409-
it('should append querystring to canonical url', function(){
410-
var el = document.createElement('link');
411-
el.rel = 'canonical';
412-
el.href = 'baz.com';
413-
head.appendChild(el);
414-
analytics.page({ search: '?querystring' });
415-
var page = analytics._invoke.args[0][1];
416-
assert('baz.com?querystring' == page.properties().url);
417-
el.parentNode.removeChild(el);
418-
})
419-
420414
it('should accept (category, name, properties, options, callback)', function (done) {
421415
defaults.category = 'category';
422416
defaults.name = 'name';
@@ -538,22 +532,27 @@ describe('Analytics', function () {
538532
assert.equal('id', page.obj.anonymousId);
539533
});
540534

535+
it('should include context.page', function(){
536+
analytics.page();
537+
var page = analytics._invoke.args[0][1];
538+
assert.deepEqual(page.context(), { page: defaults });
539+
});
540+
541541
it('should accept context.traits', function(){
542542
analytics.page({ prop: true }, { traits: { trait: true } });
543543
var page = analytics._invoke.args[0][1];
544544
assert.deepEqual(page.context(), {
545+
page: defaults,
545546
traits: { trait: true }
546547
});
547548
});
548549

549550
it('should emit page', function (done) {
550-
defaults.category = 'category';
551-
defaults.name = 'name';
552551
analytics.once('page', function (category, name, props, opts) {
553552
assert('category' === category);
554553
assert('name' === name);
555-
assert(equal(opts, {}));
556-
assert(equal(props, defaults));
554+
assert.deepEqual(opts, { context: { page: defaults } });
555+
assert.deepEqual(props, extend(defaults, { category: 'category', name: 'name' }));
557556
done();
558557
});
559558
analytics.page('category', 'name', {}, {});
@@ -755,11 +754,18 @@ describe('Analytics', function () {
755754
assert.deepEqual(app, identify.obj.context.app);
756755
});
757756

757+
it('should include context.page', function(){
758+
analytics.identify(1);
759+
var identify = analytics._invoke.args[0][1];
760+
assert.deepEqual(identify.context(), { page: contextPage });
761+
});
762+
758763
it('should accept context.traits', function(){
759764
analytics.identify(1, { trait: 1 }, { traits: { trait: true } });
760765
var identify = analytics._invoke.args[0][1];
761766
assert.deepEqual(identify.traits(), { trait: 1, id: 1 });
762767
assert.deepEqual(identify.context(), {
768+
page: contextPage,
763769
traits: { trait: true }
764770
});
765771
});
@@ -928,11 +934,18 @@ describe('Analytics', function () {
928934
assert.deepEqual(app, group.obj.context.app);
929935
});
930936

937+
it('should include context.page', function(){
938+
analytics.group(1);
939+
var group = analytics._invoke.args[0][1];
940+
assert.deepEqual(group.context(), { page: contextPage });
941+
});
942+
931943
it('should accept context.traits', function(){
932944
analytics.group(1, { trait: 1 }, { traits: { trait: true } });
933945
var group = analytics._invoke.args[0][1];
934946
assert.deepEqual(group.traits(), { trait: 1, id: 1 });
935947
assert.deepEqual(group.context(), {
948+
page: contextPage,
936949
traits: { trait: true }
937950
});
938951
});
@@ -1088,11 +1101,18 @@ describe('Analytics', function () {
10881101
assert.deepEqual({}, msg.proxy('context.campaign'));
10891102
});
10901103

1104+
it('should include context.page', function(){
1105+
analytics.track('event');
1106+
var track = analytics._invoke.args[0][1];
1107+
assert.deepEqual(track.context(), { page: contextPage });
1108+
});
1109+
10911110
it('should accept context.traits', function(){
10921111
analytics.track('event', { prop: 1 }, { traits: { trait: true } });
10931112
var track = analytics._invoke.args[0][1];
10941113
assert.deepEqual(track.properties(), { prop: 1 });
10951114
assert.deepEqual(track.context(), {
1115+
page: contextPage,
10961116
traits: { trait: true }
10971117
});
10981118
});
@@ -1386,6 +1406,12 @@ describe('Analytics', function () {
13861406
});
13871407
});
13881408

1409+
it('should include context.page', function(){
1410+
analytics.alias();
1411+
var alias = analytics._invoke.args[0][1];
1412+
assert.deepEqual(alias.context(), { page: contextPage });
1413+
});
1414+
13891415
it('should emit alias', function (done) {
13901416
analytics.once('alias', function (newId, oldId, options) {
13911417
assert('new' === newId);

0 commit comments

Comments
 (0)