Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
poteto committed Oct 26, 2015
2 parents cc0d081 + 274f4d2 commit 3ee16b6
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 9 deletions.
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ember-metrics
*Send data to multiple analytics services without re-implementing new API*

[![npm version](https://badge.fury.io/js/ember-metrics.svg)](http://badge.fury.io/js/ember-metrics) [![Build Status](https://travis-ci.org/poteto/ember-metrics.svg)](https://travis-ci.org/poteto/ember-metrics) [![Ember Observer Score](http://emberobserver.com/badges/ember-metrics.svg)](http://emberobserver.com/addons/ember-metrics)
[![npm version](https://badge.fury.io/js/ember-metrics.svg)](http://badge.fury.io/js/ember-metrics) [![Build Status](https://travis-ci.org/poteto/ember-metrics.svg?branch=master)](https://travis-ci.org/poteto/ember-metrics) [![Ember Observer Score](http://emberobserver.com/badges/ember-metrics.svg)](http://emberobserver.com/addons/ember-metrics)

This addon adds a simple `metrics` service to your app that makes it simple to send data to multiple analytics services without having to implement a new API each time.

Expand All @@ -13,9 +13,9 @@ Writing your own adapters for currently unsupported analytics services is easy t

1. `GoogleAnalytics`
- `id`: [Property ID](https://support.google.com/analytics/answer/1032385?hl=en), e.g. `UA-XXXX-Y`
2. `Mixpanel`
1. `Mixpanel`
- `token`: [Mixpanel token](https://mixpanel.com/help/questions/articles/where-can-i-find-my-project-token)
3. `GoogleTagManager`
1. `GoogleTagManager`
- `id`: [Container ID](https://developers.google.com/tag-manager/quickstart), e.g. `GTM-XXXX`

- `dataLayer`: An array containing a single POJO of information, e.g.:
Expand All @@ -25,8 +25,10 @@ Writing your own adapters for currently unsupported analytics services is easy t
'visitorType': 'high-value'
}];
```
4. `KISSMetrics` (WIP)
5. `CrazyEgg` (WIP)
1. `Segment`
- `key`: [Segment key](https://segment.com/docs/libraries/analytics.js/quickstart/)
1. `KISSMetrics` (WIP)
1. `CrazyEgg` (WIP)

## Installing The Addon

Expand All @@ -43,7 +45,7 @@ ember install:addon ember-metrics
```

## Compatibility
This addon is tested against the `release`, `beta`, and `canary` channels, as well as `~1.11.0`, and `1.12.1`.
This addon is tested against the `release`, `beta`, and `canary` channels, as well as `~1.11.0`, and `1.12.1`.

## Configuration

Expand All @@ -67,6 +69,13 @@ module.exports = function(environment) {
token: '0f76c037-4d76-4fce-8a0f-a9a8f89d1453'
}
},
{
name: 'Segment',
environments: ['production']
config: {
key: '4fce-8a0f-a9a8f89d1453'
}
},
{
name: 'LocalAdapter',
environments: ['all'] // default
Expand Down Expand Up @@ -116,6 +125,23 @@ To only activate adapters in specific environments, you can add an array of envi
- `production`
- `all` (default, will be activated in all environments)
## Content Security Policy
If you're using [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy), you'll need to modify the content security policy to allow loading of any remote scripts. In `config/environment.js`, add this to the `ENV` hash (modify as necessary):

```js
// example for loading Google Analytics
contentSecurityPolicy: {
'default-src': "'none'",
'script-src': "'self' www.google-analytics.com",
'font-src': "'self'",
'connect-src': "'self' www.google-analytics.com",
'img-src': "'self'",
'style-src': "'self'",
'media-src': "'self'"
}
```

## Usage

In order to use the addon, you must first [configure](#configuration) it, then inject it into any Object registered in the container that you wish to track. For example, you can call a `trackPage` event across all your analytics services whenever you transition into a route, like so:
Expand All @@ -138,7 +164,7 @@ const Router = Ember.Router.extend({
Ember.run.scheduleOnce('afterRender', this, () => {
const page = document.location.pathname;
const title = this.getWithDefault('currentRouteName', 'unknown');
Ember.get(this, 'metrics').trackPage({ page, title });
});
}
Expand Down
7 changes: 6 additions & 1 deletion addon/metrics-adapters/google-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default BaseAdapter.extend({
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
/* jshint ignore:end */

if (hasOptions) {
Expand All @@ -57,6 +57,11 @@ export default BaseAdapter.extend({
const sendEvent = { hitType: 'event' };
let gaEvent = {};

if (compactedOptions.nonInteraction) {
gaEvent.nonInteraction = compactedOptions.nonInteraction;
delete compactedOptions.nonInteraction;
}

for (let key in compactedOptions) {
const capitalizedKey = capitalize(key);
gaEvent[`event${capitalizedKey}`] = compactedOptions[key];
Expand Down
71 changes: 71 additions & 0 deletions addon/metrics-adapters/segment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Ember from 'ember';
import canUseDOM from '../utils/can-use-dom';
import { compact } from '../utils/object-transforms';
import BaseAdapter from './base';

const {
$,
assert,
copy,
get
} = Ember;

export default BaseAdapter.extend({
toStringExtension() {
return 'Segment';
},

init() {
const config = copy(get(this, 'config'));
const segmentKey = config.key;

assert(`[ember-metrics] You must pass a valid \`key\` to the ${this.toString()} adapter`, segmentKey);

if (canUseDOM) {
/* jshint ignore:start */
window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9";
/* jshint ignore:end */
window.analytics.load(segmentKey);
}
},

alias(options = {}) {
const compactedOptions = compact(options);
const { alias, original } = compactedOptions;

if (original) {
window.analytics.alias(alias, original);
} else {
window.analytics.alias(alias);
}
},

identify(options = {}) {
const compactedOptions = compact(options);
const { distinctId } = compactedOptions;
delete compactedOptions.distinctId;

window.analytics.identify(distinctId, compactedOptions);
},

trackEvent(options = {}) {
const compactedOptions = compact(options);
const { event } = compactedOptions;
delete compactedOptions.event;

window.analytics.track(event, compactedOptions);
},

trackPage(options = {}) {
const compactedOptions = compact(options);
const { page } = compactedOptions;
delete compactedOptions.page;

window.analytics.page(page, compactedOptions);
},

willDestroy() {
$('script[src*="segment.com"]').remove();
delete window.analytics;
}
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ember-metrics",
"version": "0.4.0",
"version": "0.5.0",
"description": "Send data to multiple analytics integrations without re-implementing new API",
"directories": {
"doc": "doc",
Expand Down
77 changes: 77 additions & 0 deletions tests/unit/metrics-adapters/segment-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { moduleFor, test } from 'ember-qunit';
import sinon from 'sinon';

let sandbox, config;

moduleFor('ember-metrics@metrics-adapter:segment', 'segment adapter', {
beforeEach() {
sandbox = sinon.sandbox.create();
config = {
key: 'SEGMENT_KEY'
};
},
afterEach() {
sandbox.restore();
}
});

test('#identify calls analytics with the right arguments', function(assert) {
const adapter = this.subject({ config });
const stub = sandbox.stub(window.analytics, 'identify', () => {
return true;
});
adapter.identify({
distinctId: 123
});
assert.ok(stub.calledWith(123), 'it sends the correct arguments');
});

test('#trackEvent returns the correct response shape', function(assert) {
const adapter = this.subject({ config });
const stub = sandbox.stub(window.analytics, 'track');
adapter.trackEvent({
event: 'Signed Up',
category: 'button',
action: 'click',
label: 'nav buttons',
value: 4
});
const expectedArguments = {
category: 'button',
action: 'click',
label: 'nav buttons',
value: 4
};

assert.ok(stub.calledWith('Signed Up', expectedArguments), 'track called with proper arguments');
});

test('#trackPage returns the correct response shape', function(assert) {
const adapter = this.subject({ config });
const stub = sandbox.stub(window.analytics, 'page');
adapter.trackPage({
page: '/my-overridden-page?id=1',
title: 'my overridden page'
});
const expectedArguments = {
title: 'my overridden page'
};

assert.ok(stub.calledWith('/my-overridden-page?id=1', expectedArguments), 'page called with proper arguments');
});

test('#trackPage returns the correct response shape', function(assert) {
const adapter = this.subject({ config });
const stub = sandbox.stub(window.analytics, 'page');
adapter.trackPage();

assert.ok(stub.calledWith(), 'page called with default arguments');
});

test('#alias returns the correct response shape', function(assert) {
const adapter = this.subject({ config });
const stub = sandbox.stub(window.analytics, 'alias');
adapter.alias({ alias: 'foo', original: 'bar' });

assert.ok(stub.calledWith('foo', 'bar'), 'page called with default arguments');
});

0 comments on commit 3ee16b6

Please sign in to comment.