Bug 978158 - Setup FHR provider for Translation project r=felipe,rnewman

--HG--
rename : browser/components/translation/test/test_cld2.js => browser/components/translation/test/unit/test_cld2.js
rename : browser/components/translation/test/xpcshell.ini => browser/components/translation/test/unit/xpcshell.ini
This commit is contained in:
Steven MacLeod 2014-06-03 18:08:35 +02:00
parent 1f554e8776
commit 867194fac2
9 changed files with 593 additions and 12 deletions

View File

@ -1,16 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["Translation"];
this.EXPORTED_SYMBOLS = [
"Translation",
"TranslationProvider",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const TRANSLATION_PREF_SHOWUI = "browser.translation.ui.show";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
this.Translation = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
@ -28,16 +38,20 @@ this.Translation = {
},
languageDetected: function(aBrowser, aDetectedLanguage) {
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) == -1 ||
aDetectedLanguage == this.defaultTargetLanguage)
return;
TranslationHealthReport.recordTranslationOpportunity(aDetectedLanguage);
if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI))
return;
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) != -1 &&
aDetectedLanguage != this.defaultTargetLanguage) {
if (!aBrowser.translationUI)
aBrowser.translationUI = new TranslationUI(aBrowser);
if (!aBrowser.translationUI)
aBrowser.translationUI = new TranslationUI(aBrowser);
aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
}
aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
}
};
@ -187,3 +201,228 @@ TranslationUI.prototype = {
}
}
};
/**
* Helper methods for recording translation data in FHR.
*/
let TranslationHealthReport = {
/**
* Record a translation opportunity in the health report.
* @param language
* The language of the page.
*/
recordTranslationOpportunity: function (language) {
this._withProvider(provider => provider.recordTranslationOpportunity(language));
},
/**
* Record a translation in the health report.
* @param langFrom
* The language of the page.
* @param langTo
* The language translated to
* @param numCharacters
* The number of characters that were translated
*/
recordTranslation: function (langFrom, langTo, numCharacters) {
this._withProvider(provider => provider.recordTranslation(langFrom, langTo, numCharacters));
},
/**
* Record a change of the detected language in the health report. This should
* only be called when actually executing a translation not every time the
* user changes in the language in the UI.
*
* @param beforeFirstTranslation
* A boolean indicating if we are recording a change of detected
* language before translating the page for the first time. If we
* have already translated the page from the detected language and
* the user has manually adjusted the detected language false should
* be passed.
*/
recordLanguageChange: function (beforeFirstTranslation) {
this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
},
/**
* Retrieve the translation provider and pass it to the given function.
*
* @param callback
* The function that will be passed the translation provider.
*/
_withProvider: function (callback) {
try {
let reporter = Cc["@mozilla.org/datareporting/service;1"]
.getService().wrappedJSObject.healthReporter;
if (reporter) {
reporter.onInit().then(function () {
callback(reporter.getProvider("org.mozilla.translation"));
}, Cu.reportError);
} else {
callback(null);
}
} catch (ex) {
Cu.reportError(ex);
}
}
};
/**
* Holds usage data about the Translation feature.
*
* This is a special telemetry measurement that is transmitted in the FHR
* payload. Data will only be recorded/transmitted when both telemetry and
* FHR are enabled. Additionally, if telemetry was previously enabled but
* is currently disabled, old recorded data will not be transmitted.
*/
function TranslationMeasurement1() {
Metrics.Measurement.call(this);
this._serializers[this.SERIALIZE_JSON].singular =
this._wrapJSONSerializer(this._serializers[this.SERIALIZE_JSON].singular);
this._serializers[this.SERIALIZE_JSON].daily =
this._wrapJSONSerializer(this._serializers[this.SERIALIZE_JSON].daily);
}
TranslationMeasurement1.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "translation",
version: 1,
fields: {
translationOpportunityCount: DAILY_COUNTER_FIELD,
pageTranslatedCount: DAILY_COUNTER_FIELD,
charactersTranslatedCount: DAILY_COUNTER_FIELD,
translationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
},
shouldIncludeField: function (field) {
if (!Services.prefs.getBoolPref("toolkit.telemetry.enabled")) {
// This measurement should only be included when telemetry is
// enabled, so we will not include any fields.
return false;
}
return field in this._fields;
},
_getDailyLastTextFieldAsJSON: function(name, date) {
let id = this.fieldID(name);
return this.storage.getDailyLastTextFromFieldID(id, date).then((data) => {
if (data.hasDay(date)) {
data = JSON.parse(data.getDay(date));
} else {
data = {};
}
return data;
});
},
_wrapJSONSerializer: function (serializer) {
let _parseInPlace = function(o, k) {
if (k in o) {
o[k] = JSON.parse(o[k]);
}
};
return function (data) {
let result = serializer(data);
// Special case the serialization of these fields so that
// they are sent as objects, not stringified objects.
_parseInPlace(result, "translationOpportunityCountsByLanguage");
_parseInPlace(result, "pageTranslatedCountsByLanguage");
return result;
}
}
});
this.TranslationProvider = function () {
Metrics.Provider.call(this);
}
TranslationProvider.prototype = Object.freeze({
__proto__: Metrics.Provider.prototype,
name: "org.mozilla.translation",
measurementTypes: [
TranslationMeasurement1,
],
recordTranslationOpportunity: function (language, date=new Date()) {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("translationOpportunityCount", date);
let langCounts = yield m._getDailyLastTextFieldAsJSON(
"translationOpportunityCountsByLanguage", date);
langCounts[language] = (langCounts[language] || 0) + 1;
langCounts = JSON.stringify(langCounts);
yield m.setDailyLastText("translationOpportunityCountsByLanguage",
langCounts, date);
}.bind(this));
},
recordTranslation: function (langFrom, langTo, numCharacters, date=new Date()) {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("pageTranslatedCount", date);
yield m.incrementDailyCounter("charactersTranslatedCount", date,
numCharacters);
let langCounts = yield m._getDailyLastTextFieldAsJSON(
"pageTranslatedCountsByLanguage", date);
let counts = langCounts[langFrom] || {};
counts["total"] = (counts["total"] || 0) + 1;
counts[langTo] = (counts[langTo] || 0) + 1;
langCounts[langFrom] = counts;
langCounts = JSON.stringify(langCounts);
yield m.setDailyLastText("pageTranslatedCountsByLanguage",
langCounts, date);
}.bind(this));
},
recordLanguageChange: function (beforeFirstTranslation) {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
if (beforeFirstTranslation) {
yield m.incrementDailyCounter("detectedLanguageChangedBefore");
} else {
yield m.incrementDailyCounter("detectedLanguageChangedAfter");
}
}.bind(this));
},
_enqueueTelemetryStorageTask: function (task) {
if (!Services.prefs.getBoolPref("toolkit.telemetry.enabled")) {
// This measurement should only be included when telemetry is
// enabled, so don't record any data.
return Promise.resolve(null);
}
return this.enqueueStorageOperation(() => {
return Task.spawn(task);
});
}
});

View File

@ -21,5 +21,9 @@ BROWSER_CHROME_MANIFESTS += [
]
XPCSHELL_TESTS_MANIFESTS += [
'test/xpcshell.ini'
'test/unit/xpcshell.ini'
]
EXTRA_PP_COMPONENTS += [
'translation.manifest',
]

View File

@ -4,7 +4,9 @@
// tests the translation infobar, using a fake 'Translation' implementation.
Components.utils.import("resource:///modules/translation/Translation.jsm");
let tmp = {};
Cu.import("resource:///modules/translation/Translation.jsm", tmp);
let {Translation} = tmp;
const kLanguagesPref = "browser.translation.neverForLanguages";
const kShowUIPref = "browser.translation.ui.show";

View File

@ -4,7 +4,9 @@
// tests the translation infobar, using a fake 'Translation' implementation.
Components.utils.import("resource:///modules/translation/Translation.jsm");
let tmp = {};
Cu.import("resource:///modules/translation/Translation.jsm", tmp);
let {Translation} = tmp;
const kShowUIPref = "browser.translation.ui.show";

View File

@ -0,0 +1,269 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource:///modules/translation/Translation.jsm", this);
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
// At end of test, restore original state.
const ORIGINAL_TELEMETRY_ENABLED = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
function run_test() {
run_next_test();
}
add_test(function setup() {
do_get_profile();
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
run_next_test();
});
do_register_cleanup(function() {
Services.prefs.setBoolPref("toolkit.telemetry.enabled",
ORIGINAL_TELEMETRY_ENABLED);
});
add_task(function test_constructor() {
let provider = new TranslationProvider();
});
// Provider can initialize and de-initialize properly.
add_task(function* test_init() {
let storage = yield Metrics.Storage("init");
let provider = new TranslationProvider();
yield provider.init(storage);
yield provider.shutdown();
yield storage.close();
});
// Test recording translation opportunities.
add_task(function* test_translation_opportunity() {
let storage = yield Metrics.Storage("opportunity");
let provider = new TranslationProvider();
yield provider.init(storage);
// Initially nothing should be configured.
let now = new Date();
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 0);
Assert.ok(!values.days.hasDay(now));
// Record an opportunity.
yield provider.recordTranslationOpportunity("fr", now);
values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("translationOpportunityCount"));
Assert.equal(day.get("translationOpportunityCount"), 1);
Assert.ok(day.has("translationOpportunityCountsByLanguage"));
let countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
Assert.equal(countsByLanguage["fr"], 1);
// Record more opportunities.
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordTranslationOpportunity("es", now);
values = yield m.getValues();
let day = values.days.getDay(now);
Assert.ok(day.has("translationOpportunityCount"));
Assert.equal(day.get("translationOpportunityCount"), 4);
Assert.ok(day.has("translationOpportunityCountsByLanguage"));
countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
Assert.equal(countsByLanguage["fr"], 3);
Assert.equal(countsByLanguage["es"], 1);
yield provider.shutdown();
yield storage.close();
});
// Test recording a translation.
add_task(function* test_record_translation() {
let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider();
yield provider.init(storage);
let now = new Date();
// Record a translation.
yield provider.recordTranslation("fr", "es", 1000, now);
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("pageTranslatedCount"));
Assert.equal(day.get("pageTranslatedCount"), 1);
Assert.ok(day.has("charactersTranslatedCount"));
Assert.equal(day.get("charactersTranslatedCount"), 1000);
Assert.ok(day.has("pageTranslatedCountsByLanguage"));
let countsByLanguage = JSON.parse(day.get("pageTranslatedCountsByLanguage"));
Assert.ok("fr" in countsByLanguage);
Assert.equal(countsByLanguage["fr"]["total"], 1);
Assert.equal(countsByLanguage["fr"]["es"], 1);
// Record more translations.
yield provider.recordTranslation("fr", "es", 1, now);
yield provider.recordTranslation("fr", "en", 2, now);
yield provider.recordTranslation("es", "en", 4, now);
values = yield m.getValues();
let day = values.days.getDay(now);
Assert.ok(day.has("pageTranslatedCount"));
Assert.equal(day.get("pageTranslatedCount"), 4);
Assert.ok(day.has("charactersTranslatedCount"));
Assert.equal(day.get("charactersTranslatedCount"), 1007);
Assert.ok(day.has("pageTranslatedCountsByLanguage"));
let countsByLanguage = JSON.parse(day.get("pageTranslatedCountsByLanguage"));
Assert.ok("fr" in countsByLanguage);
Assert.equal(countsByLanguage["fr"]["total"], 3);
Assert.equal(countsByLanguage["fr"]["es"], 2);
Assert.equal(countsByLanguage["fr"]["en"], 1);
Assert.ok("es" in countsByLanguage);
Assert.equal(countsByLanguage["es"]["total"], 1);
Assert.equal(countsByLanguage["es"]["en"], 1);
yield provider.shutdown();
yield storage.close();
});
// Test recording changing languages.
add_task(function* test_record_translation() {
let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider();
yield provider.init(storage);
let now = new Date();
// Record a language change before translation.
yield provider.recordLanguageChange(true);
// Record two language changes after translation.
yield provider.recordLanguageChange(false);
yield provider.recordLanguageChange(false);
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("detectedLanguageChangedBefore"));
Assert.equal(day.get("detectedLanguageChangedBefore"), 1);
Assert.ok(day.has("detectedLanguageChangedAfter"));
Assert.equal(day.get("detectedLanguageChangedAfter"), 2);
yield provider.shutdown();
yield storage.close();
});
// Test the payload after recording with telemetry enabled.
add_task(function* test_healthreporter_json() {
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
let reporter = yield getHealthReporter("healthreporter_json");
yield reporter.init();
try {
let now = new Date();
let provider = new TranslationProvider();
yield reporter._providerManager.registerProvider(provider);
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
yield provider.recordLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now);
Assert.ok(today in payload.data.days);
let day = payload.data.days[today];
Assert.ok("org.mozilla.translation.translation" in day);
let translations = day["org.mozilla.translation.translation"];
Assert.equal(translations["translationOpportunityCount"], 2);
Assert.equal(translations["pageTranslatedCount"], 2);
Assert.equal(translations["charactersTranslatedCount"], 2000);
Assert.ok("translationOpportunityCountsByLanguage" in translations);
Assert.equal(translations["translationOpportunityCountsByLanguage"]["fr"], 1);
Assert.equal(translations["translationOpportunityCountsByLanguage"]["es"], 1);
Assert.ok("pageTranslatedCountsByLanguage" in translations);
Assert.ok("fr" in translations["pageTranslatedCountsByLanguage"]);
Assert.equal(translations["pageTranslatedCountsByLanguage"]["fr"]["total"], 1);
Assert.equal(translations["pageTranslatedCountsByLanguage"]["fr"]["en"], 1);
Assert.ok("es" in translations["pageTranslatedCountsByLanguage"]);
Assert.equal(translations["pageTranslatedCountsByLanguage"]["es"]["total"], 1);
Assert.equal(translations["pageTranslatedCountsByLanguage"]["es"]["en"], 1);
Assert.ok("detectedLanguageChangedBefore" in translations);
Assert.equal(translations["detectedLanguageChangedBefore"], 1);
Assert.ok("detectedLanguageChangedAfter" in translations);
Assert.equal(translations["detectedLanguageChangedAfter"], 1);
} finally {
reporter._shutdown();
}
});
// Test the payload after recording with telemetry disabled.
add_task(function* test_healthreporter_json() {
Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
let reporter = yield getHealthReporter("healthreporter_json");
yield reporter.init();
try {
let now = new Date();
let provider = new TranslationProvider();
yield reporter._providerManager.registerProvider(provider);
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
yield provider.recordLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now);
Assert.ok(today in payload.data.days);
let day = payload.data.days[today];
Assert.ok("org.mozilla.translation.translation" in day);
let translations = day["org.mozilla.translation.translation"];
Assert.ok(!("translationOpportunityCount" in translations));
Assert.ok(!("pageTranslatedCount" in translations));
Assert.ok(!("charactersTranslatedCount" in translations));
Assert.ok(!("translationOpportunityCountsByLanguage" in translations));
Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
Assert.ok(!("detectedLanguageChangedBefore" in translations));
Assert.ok(!("detectedLanguageChangedAfter" in translations));
} finally {
reporter._shutdown();
}
});

View File

@ -4,3 +4,4 @@ tail =
firefox-appdir = browser
[test_cld2.js]
[test_healthreport.js]

View File

@ -0,0 +1,3 @@
#ifdef MOZ_SERVICES_HEALTHREPORT
category healthreport-js-provider-default TranslationProvider resource:///modules/translation/Translation.jsm
#endif

View File

@ -1528,6 +1528,67 @@ Example
}
org.mozilla.translation.translation
-----------------------------------
This daily measurement contains information about the usage of the translation
feature. It is a special telemetry measurement which will only be recorded in
FHR if telemetry is enabled.
Version 1
^^^^^^^^^
Daily counts are reported in the following properties:
translationOpportunityCount
Integer count of the number of opportunities there were to translate a page.
pageTranslatedCount
Integer count of the number of pages translated.
charactersTranslatedCount
Integer count of the number of characters translated.
detectedLanguageChangedBefore
Integer count of the number of times the user manually adjusted the detected
language before translating.
detectedLanguageChangedAfter
Integer count of the number of times the user manually adjusted the detected
language after having first translated the page.
Additional daily counts broken down by language are reported in the following
properties:
translationOpportunityCountsByLanguage
A mapping from language to count of opportunities to translate that
language.
pageTranslatedCountsByLanguage
A mapping from language to the counts of pages translated from that
language. Each language entry will be an object containing a "total" member
along with individual counts for each language translated to.
Example
^^^^^^^
::
"org.mozilla.translation.translation": {
"_v": 1,
"translationOpportunityCount": 134,
"pageTranslatedCount": 6,
"charactersTranslatedCount": "1126",
"detectedLanguageChangedBefore": 1,
"detectedLanguageChangedAfter": 2,
"translationOpportunityCountsByLanguage": {
"fr": 100,
"es": 34
},
"pageTranslatedCountsByLanguage": {
"fr": {
"total": 6,
"es": 5,
"en": 1
}
}
}
org.mozilla.experiments.info
----------------------------------