Bug 975235 - Send click pings for Directory Tiles (including which link, tile, other metadata) [r=adw]

Send a ping from the DirectoryLinks module on various click actions from newtab page.
This commit is contained in:
Ed Lee 2014-06-09 22:03:23 -07:00
parent 783db8b7f4
commit 389a56837a
7 changed files with 188 additions and 28 deletions

View File

@ -1472,8 +1472,12 @@ pref("browser.newtabpage.rows", 3);
// number of columns of newtab grid
pref("browser.newtabpage.columns", 3);
// directory tiles download URL
pref("browser.newtabpage.directory.source", "chrome://global/content/directoryLinks.json");
// endpoint to send newtab click reports
pref("browser.newtabpage.directory.reportClickEndPoint", "https://tiles.up.mozillalabs.com/ping/click");
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);

View File

@ -202,43 +202,54 @@ Site.prototype = {
}
Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
.add(aIndex);
// Specially count clicks on directory tiles
let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type);
if (typeIndex != -1) {
Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED")
.add(typeIndex);
}
},
/**
* Handles site click events.
*/
onClick: function Site_onClick(aEvent) {
let action;
let pinned = this.isPinned();
let tileIndex = this.cell.index;
let {button, target} = aEvent;
if (target.classList.contains("newtab-link") ||
target.parentElement.classList.contains("newtab-link")) {
// Record for primary and middle clicks
if (button == 0 || button == 1) {
this._recordSiteClicked(this.cell.index);
this._recordSiteClicked(tileIndex);
action = "click";
}
return;
}
// Only handle primary clicks for the remaining targets
if (button != 0) {
return;
else if (button == 0) {
aEvent.preventDefault();
if (target.classList.contains("newtab-control-block")) {
this.block();
action = "block";
}
else if (target.classList.contains("newtab-control-sponsored")) {
gPage.showSponsoredPanel(target);
action = "sponsored";
}
else if (pinned) {
this.unpin();
action = "unpin";
}
else {
this.pin();
action = "pin";
}
}
aEvent.preventDefault();
if (aEvent.target.classList.contains("newtab-control-block"))
this.block();
else if (target.classList.contains("newtab-control-sponsored"))
gPage.showSponsoredPanel(target);
else if (this.isPinned())
this.unpin();
else
this.pin();
// Specially count click actions for directory tiles
let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type);
if (action !== undefined && typeIndex != -1) {
if (action == "click") {
Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED")
.add(typeIndex);
}
DirectoryLinksProvider.reportLinkAction(this.link, action, tileIndex, pinned);
}
},
/**

View File

@ -26,6 +26,7 @@ skip-if = os == "mac" # Intermittent failures, bug 898317
[browser_newtab_drop_preview.js]
[browser_newtab_focus.js]
[browser_newtab_perwindow_private_browsing.js]
[browser_newtab_reportLinkAction.js]
[browser_newtab_reset.js]
[browser_newtab_search.js]
support-files =

View File

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const PRELOAD_PREF = "browser.newtab.preload";
gDirectorySource = "data:application/json," + JSON.stringify({
"en-US": [{
url: "http://organic.localhost/",
type: "organic"
}, {
url: "http://sponsored.localhost/",
type: "sponsored"
}]
});
function runTests() {
Services.prefs.setBoolPref(PRELOAD_PREF, false);
yield addNewTabPageTab();
let originalReportLinkAction = DirectoryLinksProvider.reportLinkAction;
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PRELOAD_PREF);
DirectoryLinksProvider.reportLinkAction = originalReportLinkAction;
});
let expected = {};
DirectoryLinksProvider.reportLinkAction = function(link, action, tileIndex, pinned) {
is(link.type, expected.type, "got expected type");
is(link.directoryIndex, expected.link, "got expected link index");
is(action, expected.action, "got expected action");
is(tileIndex, expected.tile, "got expected tile index");
is(pinned, expected.pinned, "got expected pinned");
executeSoon(TestRunner.next);
}
// Click the pin button on the link in the 1th tile spot
let siteNode = getCell(1).node.querySelector(".newtab-site");
let pinButton = siteNode.querySelector(".newtab-control-pin");
expected.type = "sponsored";
expected.link = 1;
expected.action = "pin";
expected.tile = 1;
expected.pinned = false;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
// Unpin that link
expected.action = "unpin";
expected.pinned = true;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
// Block the site in the 0th tile spot
let blockedSite = getCell(0).node.querySelector(".newtab-site");
let blockButton = blockedSite.querySelector(".newtab-control-block");
expected.type = "organic";
expected.link = 0;
expected.action = "block";
expected.tile = 0;
expected.pinned = false;
yield EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
yield whenPagesUpdated();
// Click the 1th link now in the 0th tile spot
expected.type = "sponsored";
expected.link = 1;
expected.action = "click";
yield EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
}

View File

@ -24,6 +24,9 @@ let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
let gWindow = window;
// Default to empty directory links
let gDirectorySource = "data:application/json,{}";
// The tests assume all three rows of sites are shown, but the window may be too
// short to actually show three rows. Resize it if necessary.
let requiredInnerHeight =
@ -95,9 +98,10 @@ function test() {
waitForExplicitFinish();
// start TestRunner.run() after directory links is downloaded and written to disk
watchLinksChangeOnce().then(() => {
TestRunner.run();
// Wait for hidden page to update with the desired links
whenPagesUpdated(() => TestRunner.run(), true);
});
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}");
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
}
/**

View File

@ -38,6 +38,12 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale";
// The preference that tells where to obtain directory links
const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
// The preference that tells where to send click reports
const PREF_DIRECTORY_REPORT_CLICK_ENDPOINT = "browser.newtabpage.directory.reportClickEndPoint";
// The preference that tells if telemetry is enabled
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
// The frecency of a directory link
const DIRECTORY_FRECENCY = 1000;
@ -229,7 +235,9 @@ let DirectoryLinksProvider = {
try {
let locale = this.locale;
let json = gTextDecoder.decode(binaryData);
output = JSON.parse(json)[locale];
let list = JSON.parse(json);
this._listId = list.id;
output = list[locale];
}
catch (e) {
Cu.reportError(e);
@ -242,6 +250,43 @@ let DirectoryLinksProvider = {
});
},
/**
* Report a click behavior on a link for an action
* @param link Link object from DirectoryLinksProvider
* @param action String of the behavior to report
* @param tileIndex Number for the tile position of the link
* @param pinned Boolean if the tile is pinned
*/
reportLinkAction: function DirectoryLinksProvider_reportLinkAction(link, action, tileIndex, pinned) {
let reportClickEndPoint;
let telemetryEnabled = false;
try {
reportClickEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_REPORT_CLICK_ENDPOINT);
telemetryEnabled = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED);
}
catch (ex) {
return;
}
if (!telemetryEnabled) {
return;
}
// Package the data to be sent with the ping
let ping = new XMLHttpRequest();
let queryParams = [
["list", this._listId || ""],
["link", link.directoryIndex],
["action", action],
["tile", tileIndex],
["score", link.frecency],
["pin", +pinned],
].map(([key, val]) => encodeURIComponent(key) + "=" + encodeURIComponent(val));
ping.open("GET", reportClickEndPoint + "?" + queryParams.join("&"));
ping.send();
},
/**
* Submits counts of shown directory links for each type and
* triggers directory download if sponsored link was shown

View File

@ -30,10 +30,8 @@ const kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
// DirectoryLinksProvider preferences
const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
// app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
Services.prefs.setCharPref(kLocalePref, "en-US");
Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
const kReportClickUrlPref = "browser.newtabpage.directory.reportClickEndPoint";
const kTelemetryEnabledPref = "toolkit.telemetry.enabled";
// httpd settings
var server;
@ -41,8 +39,16 @@ const kDefaultServerPort = 9000;
const kBaseUrl = "http://localhost:" + kDefaultServerPort;
const kExamplePath = "/exampleTest/";
const kFailPath = "/fail/";
const kReportClickPath = "/reportClick/";
const kExampleURL = kBaseUrl + kExamplePath;
const kFailURL = kBaseUrl + kFailPath;
const kReportClickUrl = kBaseUrl + kReportClickPath;
// app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
Services.prefs.setCharPref(kLocalePref, "en-US");
Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
Services.prefs.setCharPref(kReportClickUrlPref, kReportClickUrl);
Services.prefs.setBoolPref(kTelemetryEnabledPref, true);
const kHttpHandlerData = {};
kHttpHandlerData[kExamplePath] = {"en-US": [{"url":"http://example.com","title":"RemoteSource"}]};
@ -165,9 +171,31 @@ function run_test() {
DirectoryLinksProvider.reset();
Services.prefs.clearUserPref(kLocalePref);
Services.prefs.clearUserPref(kSourceUrlPref);
Services.prefs.clearUserPref(kReportClickUrlPref);
Services.prefs.clearUserPref(kTelemetryEnabledPref);
});
}
add_task(function test_reportLinkAction() {
let link = 1;
let action = "click";
let tile = 2;
let score = 3;
let pin = 1;
let expectedQuery = "list=&link=1&action=click&tile=2&score=3&pin=1"
let expectedPath = kReportClickPath;
let deferred = Promise.defer();
server.registerPrefixHandler(kReportClickPath, (aRequest, aResponse) => {
do_check_eq(aRequest.path, expectedPath);
do_check_eq(aRequest.queryString, expectedQuery);
deferred.resolve();
});
DirectoryLinksProvider.reportLinkAction({directoryIndex: link, frecency: score}, action, tile, pin);
return deferred.promise;
});
add_task(function test_fetchAndCacheLinks_local() {
yield DirectoryLinksProvider.init();
yield cleanJsonFile();