Skip to content

Commit

Permalink
Build a wrapper for submitExternalPing()
Browse files Browse the repository at this point in the history
- Add small example add-on showing how to use the ping messaging

- Switch to sdk/system/events for cleaner access to nsIObserver

- Remove lib/authRequest.js for now, because metrics pings do not go to
  the Test Pilot server any more. (Feedback also now does nothing)

- Do not ping for every message that the addon sends to web content

- Small bugfix for isTestpilotAddonID

- Misc reorganizing in require()'s at the top

Issue mozilla#479
lmorchard committed Mar 24, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 9bf2838 commit 6fb7b9e
Showing 12 changed files with 185 additions and 127 deletions.
116 changes: 41 additions & 75 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
* http://mozilla.org/MPL/2.0/.
*/

/* global Services */
/* global TelemetryController */

const settings = {};

@@ -13,23 +13,27 @@ let messageBridgePageMod;
let button;
let app;

const store = require('sdk/simple-storage').storage;
const {Cc, Ci, Cu} = require('chrome');
Cu.import('resource://gre/modules/Services.jsm');

Cu.import('resource://gre/modules/TelemetryController.jsm');

const AddonManager = Cu.import('resource://gre/modules/AddonManager.jsm').AddonManager;
const Prefs = Cu.import('resource://gre/modules/Preferences.jsm').Preferences;
const cookieManager2 = Cc['@mozilla.org/cookiemanager;1']
.getService(Ci.nsICookieManager2);

const self = require('sdk/self');
const store = require('sdk/simple-storage').storage;
const {Panel} = require('sdk/panel');
const {PageMod} = require('sdk/page-mod');
const {ToggleButton} = require('sdk/ui/button/toggle');
const request = require('sdk/request').Request;
const AddonManager = Cu.import('resource://gre/modules/AddonManager.jsm').AddonManager;
const Prefs = Cu.import('resource://gre/modules/Preferences.jsm').Preferences;
const simplePrefs = require('sdk/simple-prefs');
const URL = require('sdk/url').URL;
const cookieManager2 = Cc['@mozilla.org/cookiemanager;1']
.getService(Ci.nsICookieManager2);
const authRequest = require('./lib/auth-request');
const Mustache = require('mustache');

const Events = require('sdk/system/events');

const Mustache = require('mustache');
const templates = require('./lib/templates');
Mustache.parse(templates.feedback);
Mustache.parse(templates.experimentList);
@@ -146,11 +150,6 @@ function setupApp() {
});
}

// update the our icon for devtools themes
Prefs.observe('devtools.theme', pref => {
setToggleButton(pref === 'dark');
});

const panel = Panel({ // eslint-disable-line new-cap
contentURL: './feedback.html',
contentScriptFile: './panel.js',
@@ -179,8 +178,6 @@ panel.port.on('launch-feedback', id => {
panel.port.on('feedback-submit', dataStr => {
const data = JSON.parse(dataStr);
data.tags = ['main-addon'];
authRequest.sendMetric(settings, 'user-feedback', data,
store.availableExperiments[data.addon_id]);
panel.hide();
});

@@ -200,6 +197,10 @@ function getExperimentList(availableExperiments, installedAddons) {
});
}

// update the our icon for devtools themes
Prefs.observe('devtools.theme', pref => {
setToggleButton(pref === 'dark');
});
setToggleButton(Prefs.get('devtools.theme') === 'dark');

function setToggleButton(dark) {
@@ -228,53 +229,27 @@ function handleToolbarButtonChange(state) {
});
}

// Listen for metrics events from experiments.
const EVENT_SEND_METRIC = 'testpilot::send-metric';
const metrics = {
isInitialized: false,
init: function() {
if (this.isInitialized) {
return;
}
this.isInitialized = true;
// Note: the observer service holds a strong reference to this observer,
// so we must detach it on shutdown / uninstall by calling destroy().
Services.obs.addObserver(metrics, EVENT_SEND_METRIC, false);
},
destroy: function() {
Services.obs.removeObserver(metrics, EVENT_SEND_METRIC, false);
this.isInitialized = false;
},
// The metrics object format is { key, value, addonName }, where
// 'key' is the name of the event,
// 'value' is the value of the event (can be any JSON-serializable object),
// 'addonName' is the name of the experiment sending the data.
observe: function() {
// The nsIObserverService sends non-useful positional arguments; the third
// is the only one we need.
const data = arguments[2];

let d;
try {
d = JSON.parse(data);
} catch (ex) {
const parseErrorMessage = 'Test Pilot metrics error: cannot process ' +
'event, JSON.parse failed: ';
console.error(parseErrorMessage, ex); // eslint-disable-line no-console
return;
}

if (d && 'key' in d && 'value' in d && 'addonName' in d) {
authRequest.sendMetric(settings, d.key, d.value, d.addonName);
} else {
const clientErrorMessage = 'Test Pilot metrics error: event objects ' +
'must have key, value, and addonName properties. Object received was ';
console.error(clientErrorMessage, d); // eslint-disable-line no-console
return;
}
}
};
metrics.init();
function onMetricsPing(ev) {
const { subject, data } = ev;
const dataParsed = JSON.parse(data);

// TODO: The subject is add-on ID, could map to ping types as needed.
const pingType = 'testpilottest';

const payload = {
version: 1,
test: subject,
payload: dataParsed
};

TelemetryController.submitExternalPing(pingType, payload, {
addClientId: true,
addEnvironment: true
});
}
Events.on(EVENT_SEND_METRIC, onMetricsPing);

function Router(mod) {
this.mod = mod;
@@ -290,17 +265,8 @@ Router.prototype.on = function(name, f) {
return this;
};

Router.prototype.send = function(name, data, addon) {
Router.prototype.send = function(name, data) {
this.mod.port.emit('from-addon-to-web', {type: name, data: data});
if (addon) {
data.tags = ['main-addon'];
const packet = JSON.stringify({
key: name,
value: data,
addonName: addon
});
Services.obs.notifyObservers(null, EVENT_SEND_METRIC, packet);
}
return this;
};

@@ -387,7 +353,7 @@ function formatInstallData(install, addon) {
}

function isTestpilotAddonID(id) {
return id in store.availableExperiments;
return 'availableExperiments' in store && id in store.availableExperiments;
}

function syncAddonInstallation(addonID) {
@@ -504,12 +470,12 @@ const installListener = {
};
AddonManager.addInstallListener(installListener);

require('sdk/system/unload').when(function(reason) {
exports.onUnload = function(reason) {
AddonManager.removeAddonListener(addonListener);
AddonManager.removeInstallListener(installListener);
panel.destroy();
button.destroy();
metrics.destroy();
Events.off(EVENT_SEND_METRIC, onMetricsPing);
setInstalledFlagPageMod.destroy();
messageBridgePageMod.destroy();
if (reason === 'uninstall') {
@@ -522,4 +488,4 @@ require('sdk/system/unload').when(function(reason) {
}
delete store.availableExperiments;
}
});
};
52 changes: 0 additions & 52 deletions addon/lib/auth-request.js

This file was deleted.

2 changes: 2 additions & 0 deletions docs/examples/addon-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Test Pilot Example Experiment 1
A basic add-on
Binary file added docs/examples/addon-sdk/data/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/addon-sdk/data/icon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/examples/addon-sdk/data/icon-64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
21 changes: 21 additions & 0 deletions docs/examples/addon-sdk/data/panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!--
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the 'License'). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="panel.css" type="text/css" media="screen" />
</head>
<body>
<p>These buttons send metric pings!</p>
<ul class="pings">
<li><button id="b1">One</button></li>
<li><button id="b2">Two</button></li>
<li><button id="b3">Three</button></li>
</ul>
</body>
</html>
10 changes: 10 additions & 0 deletions docs/examples/addon-sdk/data/panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function $$ (selector, el) {
return Array.prototype.slice.call((el || document)
.querySelectorAll(selector));
}

$$('.pings li button').forEach(function (el) {
el.addEventListener('click', function (ev) {
self.port.emit('buttonClicked', el.id);
});
});
65 changes: 65 additions & 0 deletions docs/examples/addon-sdk/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const self = require("sdk/self");

const {ToggleButton} = require('sdk/ui/button/toggle');
const {Panel} = require('sdk/panel');
const Events = require("sdk/system/events");

// TODO: Move this into a shared library?
function sendMetricsPing(self, payload) {
Events.emit('testpilot::send-metric', {
subject: self.id,
data: JSON.stringify(payload)
});
}

let panel, button;

exports.main = function (options, callbacks) {

panel = Panel({ // eslint-disable-line new-cap
contentURL: './panel.html',
contentScriptFile: './panel.js',
onHide: () => {
button.state('window', {checked: false});
}
});

panel.port.on('buttonClicked', function (buttonID) {
sendMetricsPing(self, {
happening: 'buttonClicked',
buttonID: buttonID
});
});

button = ToggleButton({ // eslint-disable-line new-cap
id: "experiment-1-link",
label: "Experiment 1",
icon: {
"16": "./icon-16.png",
"32": "./icon-32.png",
"64": "./icon-64.png"
},
onChange: (state) => {

sendMetricsPing(self, {
happening: 'buttonStateChange',
currentState: state.checked
});

if (!state.checked) { return; }

panel.show({
width: 200,
height: 200,
position: button
});

}
});

};

exports.onUnload = function (reason) {
panel.destroy();
button.destroy();
};
27 changes: 27 additions & 0 deletions docs/examples/addon-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"title": "Test Pilot Example Experiment 1",
"name": "testpilot-addon-sdk-example-experiment",
"version": "0.0.1",
"description": "A basic add-on",
"main": "index.js",
"author": "Mozilla (https://mozilla.org/)",
"engines": {
"firefox": ">=38.0a1"
},
"keywords": [
"jetpack"
],
"scripts": {
"start": "npm run watch",
"once": "jpm run -b beta",
"watch": "jpm watchpost --post-url http://localhost:8888",
"lint": "eslint ."
},
"license": "MPL-2.0",
"devDependencies": {
"babel-eslint": "4.1.8",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^0.0.8",
"jpm": "1.0.6"
}
}
19 changes: 19 additions & 0 deletions docs/examples/addon-sdk/test/test-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var main = require("../");

exports["test main"] = function(assert) {
assert.pass("Unit test running!");
};

exports["test main async"] = function(assert, done) {
assert.pass("async Unit test running!");
done();
};

exports["test dummy"] = function(assert, done) {
main.dummy("foo", function(text) {
assert.ok((text === "foo"), "Is the text actually 'foo'");
done();
});
};

require("sdk/test").run(exports);

0 comments on commit 6fb7b9e

Please sign in to comment.