Bug 977770 - Count the times users manually adjust the offered translation pair. r=felipe. a=gavin
authorAsaf Romano <mano@mozilla.com>
Tue, 24 Jun 2014 16:53:30 +0300
changeset 208437 971a50fabcfc27220d27ecb367332bbaf449c0ff
parent 208436 d115bce9216939320400e98401c586dd66dadcd1
child 208438 1d198f4776a565ed9673deab6d4202ed7a85fd3b
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, gavin
bugs977770
milestone32.0a2
Bug 977770 - Count the times users manually adjust the offered translation pair. r=felipe. a=gavin
browser/components/translation/Translation.jsm
browser/components/translation/test/browser_translation_fhr.js
browser/components/translation/test/unit/test_healthreport.js
services/healthreport/docs/dataformat.rst
--- 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": {