Skip to content
This repository has been archived by the owner on Apr 26, 2018. It is now read-only.

Commit

Permalink
Initial project commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredhirsch committed Jan 26, 2017
1 parent a8e6a88 commit 1d6a8a0
Show file tree
Hide file tree
Showing 19 changed files with 1,169 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules/*
61 changes: 61 additions & 0 deletions API.md
@@ -0,0 +1,61 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

### Table of Contents

- [Metrics](#metrics)
- [sendEvent](#sendevent)

## Metrics

Class that represents a metrics event broker. Events are sent to Google
Analytics if the `tid` parameter is set. Events are sent to Mozilla's
data pipeline via the Test Pilot add-on. No metrics code changes are
needed when the experiment is added to or removed from Test Pilot.

**Parameters**

- `$0` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `$0.id` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** addon ID, e.g. '@testpilot-addon'. See <https://mdn.io/add_on_id>.
- `$0.version` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** addon version, e.g. '1.0.2'.
- `$0.uid` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** unique identifier for a specific instance of an addon.
Used as Google Analytics user ID.
- `$0.tid` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** Google Analytics tracking ID. Optional, but required
to send events to Google Analytics.
- `$0.type` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** addon type. one of: 'webextension',
'sdk', 'bootstrapped'. (optional, default `webextension`)
- `$0.debug` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** if true, enables logging. Note that this
value can be changed on a running instance, by modifying its `debug` property. (optional, default `false`)


- Throws **[SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError)** If the required properties are missing, or if the
'type' property is unrecognized.
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** if initializing the transports fails.

### sendEvent

Sends an event to the Mozilla data pipeline (and Google Analytics, if
a `tid` was passed to the constructor). Note: to avoid breaking callers,
if the required arguments are missing, an Error will not be thrown.
Instead, the message will be silently dropped. Enable debug mode to
see error messages.

If you want to pass additional fields, or use a Google Analytics hit type
other than 'event', you can transform the output yourself using the
transform parameter. You will need to add Custom Dimensions to GA for any
extra fields: <https://support.google.com/analytics/answer/2709828>

**Parameters**

- `$0.event` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** What is happening? e.g. `click`
- `$0.object` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** What is being affected? e.g. `home-button-1`
- `$0.category` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** If you want to add a category
for easy reporting later. e.g. `mainmenu` (optional, default `interactions`)
- `$0.variant` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** An identifying string if you're running
different variants. e.g. `cohort-A`
- `$0.transform` **[function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** Transform function used to alter the
parameters sent to GA. The `transform` function signature is
`transform(input, output)`, where `input` is the object passed to
`sendEvent` (excluding `transform`), and `output` is the default GA
object generated by the `_gaTransform` method. The `transform` function
should return an object whose keys are GA Measurement Protocol parameters.
The returned object will be form encoded and sent to GA.
329 changes: 327 additions & 2 deletions README.md
@@ -1,2 +1,327 @@
# testpilot-metrics
JS library that handles metrics events for Test Pilot experiments
# `testpilot-metrics`

The `testpilot-metrics` library sends pings to Google Analytics and Mozilla's
internal metrics pipeline. It is designed for use by Test Pilot experiments.


## Installation

`npm install testpilot-metrics`

The only file you need in your build chain is `testpilot-metrics.js`. It has no
dependencies.


## Google Analytics setup

1) [Create a GA account](https://www.google.com/analytics), if you don't have one.

2) [Create a mobile app GA property](https://support.google.com/analytics/answer/2614741?hl=en)
for your experiment.

- The app type has version number tracking baked in, which helps with
connecting releases to changes in usage or error rates over time.

- The app dashboard also has a real-time event view, which you can use
to watch test events land while debugging.

3) If you think you might want to take advantage of Test Pilot's [A/B testing support](https://github.com/mozilla/testpilot/blob/master/docs/experiments/variants.md),
then [create a Custom Dimension](https://support.google.com/analytics/answer/2709829?hl=en#set_up_custom_dimensions)
that you can use to track variant information. By convention, the first custom
dimension, `cd1`, should be used by `testpilot-metrics` for recording variant info.
You can use the higher values for other experiment-specific extra fields you
want to send to GA.


## Usage

### Quick start

Here's a simple node-style example:

```js

const Metrics = require('testpilot-metrics');

const { sendEvent } = new Metrics({
id: '@my-addon',
version: '1.0.2a',
uid: 'some-non-PII-user-ID',
tid: 'UA-XXXXXXXX-YY'
});

sendEvent({
object: 'webext-button',
event: 'click'
});
```

### Sending extra fields in addition to event / object / category

If you need to send fields in addition to the defaults, you'll need to follow
different steps for GA and for Mozilla's data pipeline.

#### Mozilla support

For Mozilla's data pipeline, you'll need to define a Redshift schema that
includes all parameters, their types, and their size. See the [Test Pilot
metrics docs](https://github.com/mozilla/testpilot/tree/master/docs/metrics)
for more details.

Once you've defined the storage schema, you can simply insert extra fields as
top-level keys in the `sendEvent` parameter object, for example:

```js

// Track the `clientX` and `clientY` values of an experiment popup window,
// and send along with the event, object, and category:

sendEvent({
object: 'special-button',
event: 'click',
clientX: 185,
clientY: 560
});
```

Sending custom fields to Google Analytics requires one additional step.

#### Advanced Google Analytics support: the `transform` function parameter

Google Analytics doesn't support arbitrary named parameters. To send extra
fields to GA, you must decide how to map your extra fields to the GA [custom
fields](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#customs).

Once you've figured out this mapping, pass in a `transform` function that
will convert the extra fields to GA custom fields.

The `transform` function is passed 2 arguments: first, the raw event object
that was passed to `sendEvent`; second, the default GA ping that would normally
be submitted (ignoring any extra parameters). The `transform` function should
return a JS object whose keys are the GA parameters. The `testpilot-metrics`
library will then encode and send the `transform` function's output.

Note that the first custom dimension, `cd1`, is reserved for variant testing.
So, taking the example from the last section, you might map the extra `clientX`
field to `cd2`, and `clientY` to `cd3`, and otherwise leave the GA event object
alone. You'd do this:

```js

sendEvent({
object: 'special-button',
event: 'click',
clientX: 185,
clientY: 560,
transform: (input, output) => {
// Add two extra fields from the input object to the output object.
output.cd2 = input.clientX;
output.cd3 = input.clientY;
// Return the transformed output object, to be encoded and sent to GA.
return output;
}
});
```

Google Analytics defines [8 different hit types](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#t),
but this library only uses the `event` type by default. Transform functions
allow experiment authors to submit other hit types. For example, uncaught
Errors could be sent using the GA `exception` type:

```js

try {
somethingImportant();
} catch (err) {
console.error(`somethingImportant failed: ${ex}`);
sendEvent({
// This (event, object) pair will be sent to Mozilla's internal metrics.
event: 'uncaught-exception',
object: 'somethingImportant',
transform: (input, output) => {
// Change the event type.
output.t = 'exception';
// Add the exception description field, mandatory for 'exception' hits.
output.exd = `somethingImportant: ${err}`
return output;
}
});
}
```


### API docs

See [API.md](API.md).


### WebExtension Usage

Follow the steps below to add `testpilot-metrics` to your WebExtension. You can
also look at the [example WebExtension](./examples/webextension) in this repo for a more in-depth example.


1) Add the `testpilot-metrics.js` file to your project :-)

2) Add the GA URL permission to your manifest.json:

```js
// manifest.json
{ ...
"permissions": [ "https://ssl.google-analytics.com/collect" ],
...
}
```

3) Add the `testpilot-metrics.js` file to the list of background scripts in manifest.json, _before_ the background script that will use the library:

```js
// manifest.json
{ ...
"background": {
"scripts": [ "testpilot-metrics.js", "background.js" ]
},
...
}
```

4) In your startup code, call the Metrics constructor, passing in your add-on's
ID (`id`) and version (`version`), a non-PII user ID (`uid`), and, if you are
using Google Analytics, a Google Analytics tracking ID (`tid`):

```js
// background.js startup
const { sendEvent } = new Metrics({
id: 'webextension-example@testpilot.metrics',
version: '0.0.1',
tid: 'UA-XXXXXXXX-YY',
uid: '123-456-7890' // this can be any non-PII identifier that is stable over time
});
```

5) Each time an interesting event occurs, call `sendEvent`, passing in event
details in event/object/category format. If you are running any multivariate
or A/B tests, you can include that info as well:

```js
// Example click handler, somewhere in background.js
browser.browserAction.onClicked.addListener((evt) => {
sendEvent({
object: 'webext-button',
event: 'click',

// these fields are optional:
category: 'toolbar',
variant: 'green-button'
});
});
```

### SDK Usage

Follow the steps below to add `testpilot-metrics` to your SDK add-on. You can
also look at the [example SDK add-on](./examples/sdk) in this repo for a more in-depth example.

1) Add the `testpilot-metrics.js` file to your project :-)

2) Load the library using the SDK loader:

```js
const Metrics = require('testpilot-metrics');
```

3) In your startup code, call the Metrics constructor, passing in your add-on's
ID (`id`) and version (`version`), a non-PII user ID (`uid`), and, if you are
using Google Analytics, a Google Analytics tracking ID (`tid`):

```js
const { sendEvent } = new Metrics({
id: 'sdk-example@testpilot.metrics',
version: '1.0.2',
tid: 'UA-XXXXXXXX-YY',
uid: '123-456-7890' // this can be any non-PII identifier that is stable over time
});
```

4) Each time an interesting event occurs, call `sendEvent`, passing in event
details in event/object/category format. If you are running any multivariate
or A/B tests, you can include that info as well:

```js
const btn = ui.ActionButton({
id: 'metrics-test-button',
label: 'Metrics Test Button',
icon: {
'16': './icon-16.png',
'32': './icon-32.png',
'64': './icon-64.png'
},
onClick: () => {
sendEvent({
object: 'sdk-button',
event: 'click',

// these fields are optional:
category: 'interactions',
variant: 'green-button'
});
}
});
```

### Google Analytics output

For simplicity, the only GA ping type currently supported is the `event` type.

As an example, the following ping:

```js

const { sendEvent } = new Metrics({
id: '@my-addon',
version: '1.2.3',
tid: 'UA-XXXXXXXX-YY',
uid: '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
});

sendEvent({
event: 'click',
object: 'home-button-1',
category: 'toolbar-menu',
study: 'button-color-test',
variant: 'green-button'
});
```

is transformed into a GA Measurement Protocol 'event' ping with parameters:

```js
msg = {
v: 1,
an: '@my-addon',
av: '1.2.3',
tid: 'UA-XXXXXXXX-YY',
uid: '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
t: 'event',
ec: 'toolbar-menu',
ea: 'click',
el: 'home-button-1',
cd1: 'green-button'
}
```

These parameters are URL encoded before sending.

## Interested in contributing?

Grab a bug and/or say hello in the testpilot channel on Mozilla IRC :-)

You can run tests via `npm run test`.

## License

MPL 2.0

## Author

Brought to you by @6a68 and the Test Pilot team at Mozilla.
2 changes: 2 additions & 0 deletions examples/sdk/.gitignore
@@ -0,0 +1,2 @@
testpilot-metrics.js
test-pilot-metrics-sdk-example.xpi

0 comments on commit 1d6a8a0

Please sign in to comment.