Skip to content

Commit

Permalink
feat: Initial implementation of metrics pings
Browse files Browse the repository at this point in the history
* Create `lib/metrics.js` to organize metrics pings & tab state tracking
* Add metrics pings to `background.js`
* Include time option type ID in snoozed tab data for `snooze` event
* Include tab ID in snoozed tab data when snoozed for `resnooze` event
* Add first draft of docs/metrics.md
* Tests for `lib/metrics.js`

Fixes bwinton#45.
Fixes bwinton#46.
  • Loading branch information
lmorchard committed Jan 25, 2017
1 parent 938a207 commit 0559094
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
node_modules
*.zip
*.sw?
npm-debug.log
122 changes: 122 additions & 0 deletions docs/metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# METRICS

## Data Analysis

The collected data will primarily be used to answer the following questions.
Images are used for visualization and are not composed of actual data.

### Do people use this?

What is the overall engagement of Snooze Tabs? **This is the standard Daily
Active User (DAU) and Monthly Active User (MAU) analysis.** This captures data
from the people who have the add-on installed, regardless of whether they are
actively interacting with it.

### Immediate Questions

TBD

### Follow-up Questions

TBD

## Data Collection

### Server Side
There is currently no server side component to Snooze Tabs.

### Client Side
Snooze Tabs will use Test Pilot's Telemetry wrapper with no batching of data.
Details of when pings are sent are below, along with examples of the `payload`
portion of a `testpilottest` telemetry ping for each scenario.

* The user opens the snooze panel
```js
{ "event": "panel-opened" }
```

* The user snoozes a tab, choosing either a pre-defined time or a custom time
```js
{
"event": "snoozed",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A snoozed tab wakes up as a live tab
```js
{
"event": "woken",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A previously snoozed tab is focused
```js
{
"event": "focused",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A previously snoozed tab is closed without having been focused
```js
{
"event": "closed-unfocused",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A previously snoozed tab is snoozed again after waking, but not tracked after browser restart or if the tab was closed and reopened
```js
{
"event": "resnoozed",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A snoozed tab is cancelled from the management panel
```js
{
"event": "cancelled",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A snoozed tab is updated from the management panel
```js
{
"event": "updated",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

* A snoozed tab is clicked from the management panel
```js
{
"event": "clicked",
"snooze_time": 1484345836165,
"snooze_time_type": "tomorrow"
}
```

A Redshift schema for the payload:

```lua
local schema = {
-- column name field type length attributes field name
{"event", "VARCHAR", 255, nil, "Fields[payload.event]"},
{"snooze_time", "INTEGER", nil, nil, "Fields[payload.snooze_time]"},
{"snooze_time_type", "VARCHAR", 255, nil, "Fields[payload.snooze_time_type]"},
}
```

All Mozilla data is kept by default for 180 days and in accordance with our
privacy policies.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"scripts": {
"start": "npm run build && npm-run-all --parallel test watch run storybook",
"start-win": "npm run build && npm run watch",
"start-win": "npm run build && npm-run-all --parallel test watch",
"build": "npm run clean && npm-run-all --parallel copy:assets bundle:*",
"run": "web-ext run -s dist --firefox=nightly",
"clean": "rm -rf dist && shx mkdir -p dist/popup",
Expand All @@ -73,7 +73,7 @@
"storybook": "start-storybook -s ./dist -p 6006",
"build-storybook": "build-storybook",
"test": "npm run lint && npm run test:js",
"test:js": "mocha --compilers js:babel-register --require test/.setup.js --recursive"
"test:js": "mocha --compilers js:babel-register --recursive"
},
"dependencies": {
"classnames": "^2.2.5",
Expand Down
22 changes: 18 additions & 4 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import moment from 'moment';
import { NEXT_OPEN, PICK_TIME, times, timeForId } from './lib/times';
import Metrics from './lib/metrics';

const DEBUG = (process.env.NODE_ENV === 'development');
const WAKE_ALARM_NAME = 'snooze-wake-alarm';
Expand Down Expand Up @@ -43,6 +44,7 @@ function updateButtonForTab(tabId, changeInfo) {

function init() {
log('init()');
Metrics.init(window.BroadcastChannel, browser.tabs);
browser.alarms.onAlarm.addListener(handleWake);
browser.notifications.onClicked.addListener(handleNotificationClick);
browser.runtime.onMessage.addListener(handleMessage);
Expand Down Expand Up @@ -91,14 +93,25 @@ const idForItem = item => `${item.time}-${item.url}`;

const messageOps = {
schedule: message => {
Metrics.scheduleSnoozedTab(message);
const toSave = {};
toSave[idForItem(message)] = message;
return browser.storage.local.set(toSave).then(updateWakeAndBookmarks);
},
cancel: message =>
browser.storage.local.remove(idForItem(message)).then(updateWakeAndBookmarks),
update: message =>
messageOps.cancel(message.old).then(() => messageOps.schedule(message.updated))
cancel: message => {
Metrics.cancelSnoozedTab(message);
return browser.storage.local.remove(idForItem(message)).then(updateWakeAndBookmarks);
},
update: message => {
Metrics.updateSnoozedTab(message);
return messageOps.cancel(message.old).then(() => messageOps.schedule(message.updated));
},
click: message => {
Metrics.clickSnoozedTab(message);
},
panelOpened: () => {
Metrics.panelOpened();
}
};

function syncBookmarks(items) {
Expand Down Expand Up @@ -175,6 +188,7 @@ function handleWake() {
windowId: windowIds.includes(item.windowId) ? item.windowId : undefined
};
return browser.tabs.create(createProps).then(tab => {
Metrics.tabWoken(item, tab);
browser.tabs.executeScript(tab.id, {
'code': `
function flip(newUrl) {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/MainPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default class MainPanel extends React.Component {
return;
}
const [time, ] = timeForId(moment(), item.id);
scheduleSnoozedTab(time);
scheduleSnoozedTab(time, item.id);
}

closeTimeSelect() {
Expand All @@ -67,6 +67,6 @@ export default class MainPanel extends React.Component {
confirmTimeSelect(dateChoice) {
const { scheduleSnoozedTab } = this.props;
if (!dateChoice) { return; }
scheduleSnoozedTab(dateChoice);
scheduleSnoozedTab(dateChoice, PICK_TIME);
}
}
99 changes: 99 additions & 0 deletions src/lib/metrics.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,103 @@
// Track recent tabs woken to see whether they're later closed or focused
let unseenWakeHistory = {};
let seenWakeHistory = {};

// BroadcastChannel for sending metrics pings to Test Pilot add-on
let pingChannel = null;

// Name of the metrics BroadcastChannel expected by the Test Pilot add-on
const TESTPILOT_TELEMETRY_CHANNEL = 'testpilot-telemetry';

const DEBUG = (process.env.NODE_ENV === 'development');

function log(...args) {
if (DEBUG) { console.log('SnoozeTabs (Metrics):', ...args); } // eslint-disable-line no-console
}

export default {
init(BroadcastChannel, tabs) {
unseenWakeHistory = {};
seenWakeHistory = {};
pingChannel = new BroadcastChannel(TESTPILOT_TELEMETRY_CHANNEL);
tabs.onActivated.addListener(this.handleTabActivated.bind(this));
tabs.onRemoved.addListener(this.handleTabRemoved.bind(this));
log('init()');
},

postMessage(message) {
log('postMessage', message);
return (!pingChannel) ? null : pingChannel.postMessage(message);
},

handleTabActivated(activeInfo) {
const tabId = activeInfo.tabId;
if (!(tabId in unseenWakeHistory)) { return; }

const item = unseenWakeHistory[tabId];
delete unseenWakeHistory[tabId];
seenWakeHistory[tabId] = item;

this.wokenTabFocused(item);
},

handleTabRemoved(tabId/*, removeInfo */) {
if (tabId in seenWakeHistory) {
delete seenWakeHistory[tabId];
}

if (!(tabId in unseenWakeHistory)) { return; }

const item = unseenWakeHistory[tabId];
delete unseenWakeHistory[tabId];

this.wokenTabClosed(item);
},

panelOpened() {
this.postMessage({ event: 'panel-opened' });
},

_commonTabPostMessage(event, item) {
this.postMessage({
event,
snooze_time: item.time,
snooze_time_type: item.timeType
});
},

clickSnoozedTab(item) {
this._commonTabPostMessage('clicked', item);
},

cancelSnoozedTab(item) {
this._commonTabPostMessage('cancelled', item);
},

scheduleSnoozedTab(item) {
const tabId = item.tabId;
if (tabId in seenWakeHistory || tabId in unseenWakeHistory) {
delete seenWakeHistory[tabId];
delete unseenWakeHistory[tabId];
this._commonTabPostMessage('resnoozed', item);
} else {
this._commonTabPostMessage('snoozed', item);
}
},

updateSnoozedTab(item) {
this._commonTabPostMessage('updated', item);
},

tabWoken(item, tab) {
unseenWakeHistory[tab.id] = item;
this._commonTabPostMessage('woken', item);
},

wokenTabFocused(item) {
this._commonTabPostMessage('focused', item);
},

wokenTabClosed(item) {
this._commonTabPostMessage('closed-unfocused', item);
}
};
9 changes: 8 additions & 1 deletion src/popup/snooze-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function log(...args) {
if (DEBUG) { console.log('SnoozeTabs (FE):', ...args); } // eslint-disable-line no-console
}

function scheduleSnoozedTab(time) {
function scheduleSnoozedTab(time, timeType) {
browser.tabs.query({currentWindow: true}).then(tabs => {
let addBlank = true;
const closers = [];
Expand All @@ -45,8 +45,10 @@ function scheduleSnoozedTab(time) {
op: 'schedule',
message: {
'time': time.valueOf(),
'timeType': timeType,
'title': tab.title || 'Tab woke up…',
'url': tab.url,
'tabId': tab.id,
'windowId': tab.windowId,
'icon': tab.favIconUrl
}
Expand Down Expand Up @@ -74,6 +76,10 @@ function openSnoozedTab(item) {
active: true,
url: item.url
});
browser.runtime.sendMessage({
op: 'click',
message: item
});
}

function cancelSnoozedTab(item) {
Expand Down Expand Up @@ -140,6 +146,7 @@ function init() {
});
fetchEntries();
render();
browser.runtime.sendMessage({ op: 'panelOpened' });
}

init();
24 changes: 0 additions & 24 deletions test/.setup.js

This file was deleted.

Loading

0 comments on commit 0559094

Please sign in to comment.