Bug 977770 - Count the times users manually adjust the offered translation pair. r=felipe. a=gavin
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -119,16 +119,26 @@ TranslationUI.prototype = {
translate: function(aFrom, aTo) {
if (aFrom == aTo ||
(this.state == Translation.STATE_TRANSLATED &&
this.translatedFrom == aFrom && this.translatedTo == aTo)) {
// Nothing to do.
return;
}
+ if (this.state == Translation.STATE_OFFER) {
+ if (this.detectedLanguage != aFrom)
+ TranslationHealthReport.recordDetectedLanguageChange(true);
+ } else {
+ if (this.translatedFrom != aFrom)
+ TranslationHealthReport.recordDetectedLanguageChange(false);
+ if (this.translatedTo != aTo)
+ TranslationHealthReport.recordTargetLanguageChange();
+ }
+
this.state = Translation.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
this.browser.messageManager.sendAsyncMessage(
"Translation:TranslateDocument",
{ from: aFrom, to: aTo }
);
@@ -298,28 +308,37 @@ let TranslationHealthReport = {
* 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
+ * 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));
+ recordDetectedLanguageChange: function (beforeFirstTranslation) {
+ this._withProvider(provider => provider.recordDetectedLanguageChange(beforeFirstTranslation));
+ },
+
+ /**
+ * Record a change of the target 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.
+ */
+ recordTargetLanguageChange: function () {
+ this._withProvider(provider => provider.recordTargetLanguageChange());
},
/**
* Record a denied translation offer.
*/
recordDeniedTranslationOffer: function () {
this._withProvider(provider => provider.recordDeniedTranslationOffer());
},
@@ -384,16 +403,17 @@ TranslationMeasurement1.prototype = Obje
missedTranslationOpportunityCount: DAILY_COUNTER_FIELD,
pageTranslatedCount: DAILY_COUNTER_FIELD,
charactersTranslatedCount: DAILY_COUNTER_FIELD,
translationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
missedTranslationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
+ targetLanguageChanged: DAILY_COUNTER_FIELD,
deniedTranslationOffer: DAILY_COUNTER_FIELD,
showOriginalContent: DAILY_COUNTER_FIELD,
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
},
shouldIncludeField: function (field) {
if (!Services.prefs.getBoolPref("toolkit.telemetry.enabled")) {
@@ -509,29 +529,38 @@ TranslationProvider.prototype = Object.f
langCounts[langFrom] = counts;
langCounts = JSON.stringify(langCounts);
yield m.setDailyLastText("pageTranslatedCountsByLanguage",
langCounts, date);
}.bind(this));
},
- recordLanguageChange: function (beforeFirstTranslation) {
+ recordDetectedLanguageChange: 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));
},
+ recordTargetLanguageChange: function () {
+ let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
+ TranslationMeasurement1.prototype.version);
+
+ return this._enqueueTelemetryStorageTask(function* recordTask() {
+ yield m.incrementDailyCounter("targetLanguageChanged");
+ }.bind(this));
+ },
+
recordDeniedTranslationOffer: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("deniedTranslationOffer");
}.bind(this));
},
--- a/browser/components/translation/test/browser_translation_fhr.js
+++ b/browser/components/translation/test/browser_translation_fhr.js
@@ -28,17 +28,20 @@ let MetricsChecker = {
throw this._midnightError;
}
// .get() may return `undefined`, which we can't compute.
this._metrics = {
pageCount: day.get("pageTranslatedCount") || 0,
charCount: day.get("charactersTranslatedCount") || 0,
deniedOffers: day.get("deniedTranslationOffer") || 0,
- showOriginal: day.get("showOriginalContent") || 0
+ showOriginal: day.get("showOriginalContent") || 0,
+ detectedLanguageChangedBefore: day.get("detectedLanguageChangedBefore") || 0,
+ detectedLanguageChangeAfter: day.get("detectedLanguageChangedAfter") || 0,
+ targetLanguageChanged: day.get("targetLanguageChanged") || 0
};
this._metricsTime = metricsTime;
}),
checkAdditions: Task.async(function* (additions) {
let prevMetrics = this._metrics, prevMetricsTime = this._metricsTime;
try {
yield this.updateMetrics();
@@ -67,26 +70,26 @@ add_task(function* setup() {
registerCleanupFunction(() => {
Services.prefs.clearUserPref("toolkit.telemetry.enabled");
Services.prefs.clearUserPref("browser.translation.detectLanguage");
Services.prefs.clearUserPref("browser.translation.ui.show");
});
// Make sure there are some initial metrics in place when the test starts.
- yield translate("<h1>Hallo Welt!</h1>", "de", "en");
+ yield translate("<h1>Hallo Welt!</h1>", "de");
yield MetricsChecker.updateMetrics();
});
add_task(function* test_fhr() {
// Translate a page.
- yield translate("<h1>Hallo Welt!</h1>", "de", "en");
+ yield translate("<h1>Hallo Welt!</h1>", "de");
// Translate another page.
- yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", "en");
+ yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de");
yield MetricsChecker.checkAdditions({ pageCount: 1, charCount: 21, deniedOffers: 0});
});
add_task(function* test_deny_translation_metric() {
function* offerAndDeny(elementAnonid) {
let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "de", "en");
getInfobarElement(tab.linkedBrowser, elementAnonid).doCommand();
yield MetricsChecker.checkAdditions({ deniedOffers: 1 });
@@ -95,31 +98,72 @@ add_task(function* test_deny_translation
yield offerAndDeny("notNow");
yield offerAndDeny("neverForSite");
yield offerAndDeny("neverForLanguage");
yield offerAndDeny("closeButton");
// Test that the close button doesn't record a denied translation if
// the infobar is not in its "offer" state.
- let tab =
- yield translate("<h1>Hallo Welt!</h1>", "de", "en", false);
+ let tab = yield translate("<h1>Hallo Welt!</h1>", "de", false);
yield MetricsChecker.checkAdditions({ deniedOffers: 0 });
gBrowser.removeTab(tab);
});
add_task(function* test_show_original() {
- let tab =
- yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", "en", false);
+ let tab =
+ yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", false);
yield MetricsChecker.checkAdditions({ pageCount: 1, showOriginal: 0 });
getInfobarElement(tab.linkedBrowser, "showOriginal").doCommand();
yield MetricsChecker.checkAdditions({ pageCount: 0, showOriginal: 1 });
gBrowser.removeTab(tab);
});
+add_task(function* test_language_change() {
+ for (let i of Array(4)) {
+ let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "fr");
+ let browser = tab.linkedBrowser;
+ // In the offer state, translation is executed by the Translate button,
+ // so we expect just a single recoding.
+ let detectedLangMenulist = getInfobarElement(browser, "detectedLanguage");
+ simulateUserSelectInMenulist(detectedLangMenulist, "de");
+ simulateUserSelectInMenulist(detectedLangMenulist, "it");
+ simulateUserSelectInMenulist(detectedLangMenulist, "de");
+ yield acceptTranslationOffer(tab);
+
+ // In the translated state, a change in the form or to menulists
+ // triggers re-translation right away.
+ let fromLangMenulist = getInfobarElement(browser, "fromLanguage");
+ simulateUserSelectInMenulist(fromLangMenulist, "it");
+ simulateUserSelectInMenulist(fromLangMenulist, "de");
+
+ // Selecting the same item shouldn't count.
+ simulateUserSelectInMenulist(fromLangMenulist, "de");
+
+ let toLangMenulist = getInfobarElement(browser, "toLanguage");
+ simulateUserSelectInMenulist(toLangMenulist, "fr");
+ simulateUserSelectInMenulist(toLangMenulist, "en");
+ simulateUserSelectInMenulist(toLangMenulist, "it");
+
+ // Selecting the same item shouldn't count.
+ simulateUserSelectInMenulist(toLangMenulist, "it");
+
+ // Setting the target language to the source language is a no-op,
+ // so it shouldn't count.
+ simulateUserSelectInMenulist(toLangMenulist, "de");
+
+ gBrowser.removeTab(tab);
+ }
+ yield MetricsChecker.checkAdditions({
+ detectedLanguageChangedBefore: 4,
+ detectedLanguageChangeAfter: 8,
+ targetLanguageChanged: 12
+ });
+});
+
function getInfobarElement(browser, anonid) {
let notif = browser.translationUI
.notificationBox.getNotificationWithValue("translation");
return notif._getAnonElt(anonid);
}
function offerTranslatationFor(text, from) {
return Task.spawn(function* task_offer_translation() {
@@ -135,29 +179,28 @@ function offerTranslatationFor(text, fro
Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
originalShown: true,
detectedLanguage: from});
return tab;
});
}
-function acceptTranslationOffer(tab, to) {
+function acceptTranslationOffer(tab) {
return Task.spawn(function* task_accept_translation_offer() {
let browser = tab.linkedBrowser;
- getInfobarElement(browser, "toLanguage").value = to;
- getInfobarElement(browser, "toLanguage").doCommand();
+ getInfobarElement(browser, "translate").doCommand();
yield waitForMessage(browser, "Translation:Finished");
});
}
-function translate(text, from, to, closeTab = true) {
+function translate(text, from, closeTab = true) {
return Task.spawn(function* task_translate() {
let tab = yield offerTranslatationFor(text, from);
- yield acceptTranslationOffer(tab, to);
+ yield acceptTranslationOffer(tab);
if (closeTab) {
gBrowser.removeTab(tab);
} else {
return tab;
}
});
}
@@ -175,8 +218,13 @@ function promiseBrowserLoaded(browser) {
browser.addEventListener("load", function onLoad(event) {
if (event.target == browser.contentDocument) {
browser.removeEventListener("load", onLoad, true);
resolve();
}
}, true);
});
}
+
+function simulateUserSelectInMenulist(menulist, value) {
+ menulist.value = value;
+ menulist.doCommand();
+}
--- a/browser/components/translation/test/unit/test_healthreport.js
+++ b/browser/components/translation/test/unit/test_healthreport.js
@@ -168,34 +168,40 @@ add_task(function* test_record_translati
// 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 a change to the source language changes before translation.
+ yield provider.recordDetectedLanguageChange(true);
- // Record two language changes after translation.
- yield provider.recordLanguageChange(false);
- yield provider.recordLanguageChange(false);
+ // Record two changes to the source language changes after translation.
+ yield provider.recordDetectedLanguageChange(false);
+ yield provider.recordDetectedLanguageChange(false);
+ // Record two changes to the target language.
+ yield provider.recordTargetLanguageChange();
+ yield provider.recordTargetLanguageChange();
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);
+ Assert.ok(day.has("targetLanguageChanged"));
+ Assert.equal(day.get("targetLanguageChanged"), 2);
yield provider.shutdown();
yield storage.close();
});
function* test_simple_counter(aProviderFuncName, aCounterName) {
let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider();
@@ -271,19 +277,19 @@ add_task(function* test_healthreporter_j
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.recordDetectedLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
- yield provider.recordLanguageChange(false);
+ yield provider.recordDetectedLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield provider.recordShowOriginalContent();
@@ -337,19 +343,19 @@ add_task(function* test_healthreporter_j
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.recordDetectedLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
- yield provider.recordLanguageChange(false);
+ yield provider.recordDetectedLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield provider.recordShowOriginalContent();
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1551,16 +1551,19 @@ pageTranslatedCount
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.
+targetLanguageChanged
+ Integer count of the number of times the user manually adjusted the target
+ language.
deniedTranslationOffer
Integer count of the number of times the user opted-out offered
page translation, either by the Not Now button or by the notification's
close button in the "offer" state.
showOriginalContent
Integer count of the number of times the user activated the Show Original
command.
@@ -1594,16 +1597,17 @@ Example
"_v": 1,
"detectLanguageEnabled": 1,
"showTranslationUI": 1,
"translationOpportunityCount": 134,
"pageTranslatedCount": 6,
"charactersTranslatedCount": "1126",
"detectedLanguageChangedBefore": 1,
"detectedLanguageChangedAfter": 2,
+ "targetLanguageChanged": 0,
"deniedTranslationOffer": 3,
"showOriginalContent": 2,
"translationOpportunityCountsByLanguage": {
"fr": 100,
"es": 34
},
"pageTranslatedCountsByLanguage": {
"fr": {