Bug 1463087 - Instrument inspection of JS execution in the Web Console with event telemetry r?yulia draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Thu, 24 May 2018 16:22:47 +0100
changeset 802115 fee1f20bd9b306873a60085cf548154afc303c59
parent 802111 50cde18b23b02f594422e89bdeda12e2806134e7
push id111823
push usermratcliffe@mozilla.com
push dateThu, 31 May 2018 12:01:40 +0000
reviewersyulia
bugs1463087
milestone62.0a1
Bug 1463087 - Instrument inspection of JS execution in the Web Console with event telemetry r?yulia MozReview-Commit-ID: 2lri9Kzso7Q
devtools/client/webconsole/components/JSTerm.js
devtools/client/webconsole/test/mochitest/browser_jsterm_multiline.js
toolkit/components/telemetry/Events.yaml
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -14,16 +14,17 @@ loader.lazyRequireGetter(this, "defer", 
 loader.lazyRequireGetter(this, "Debugger", "Debugger");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
 loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
+loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 // Constants used for defining the direction of JSTerm input history navigation.
 const HISTORY_BACK = -1;
 const HISTORY_FORWARD = 1;
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
@@ -138,16 +139,18 @@ class JSTerm extends Component {
     this.completeNode = null;
 
     this.COMPLETE_FORWARD = 0;
     this.COMPLETE_BACKWARD = 1;
     this.COMPLETE_HINT_ONLY = 2;
     this.COMPLETE_PAGEUP = 3;
     this.COMPLETE_PAGEDOWN = 4;
 
+    this._telemetry = new Telemetry();
+
     EventEmitter.decorate(this);
     hud.jsterm = this;
   }
 
   componentDidMount() {
     let autocompleteOptions = {
       onSelect: this.onAutocompleteSelect.bind(this),
       onClick: this.acceptProposedCompletion.bind(this),
@@ -477,16 +480,21 @@ class JSTerm extends Component {
     let evalOptions = {
       bindObjectActor: options.bindObjectActor,
       frameActor: frameActor,
       selectedNodeActor: options.selectedNodeActor,
       selectedObjectActor: options.selectedObjectActor,
     };
 
     this.webConsoleClient.evaluateJSAsync(str, onResult, evalOptions);
+
+    this._telemetry.recordEvent("devtools.main", "execute_js", "webconsole", null, {
+      lines: str.split(/\n/).length
+    });
+
     return deferred.promise;
   }
 
   /**
    * Copy the object/variable by invoking the server
    * which invokes the `copy(variable)` command and makes it
    * available in the clipboard
    * @param evalString - string which has the evaluation string to be copied
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_multiline.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_multiline.js
@@ -6,16 +6,17 @@
 // Tests that the console waits for more input instead of evaluating
 // when valid, but incomplete, statements are present upon pressing enter
 // -or- when the user ends a line with shift + enter.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/mochitest/test-console.html";
+const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
 
 let SHOULD_ENTER_MULTILINE = [
   {input: "function foo() {" },
   {input: "var a = 1," },
   {input: "var a = 1;", shiftKey: true },
   {input: "function foo() { }", shiftKey: true },
   {input: "function" },
   {input: "(x) =>" },
@@ -36,17 +37,57 @@ let SHOULD_EXECUTE = [
   {input: "99 + 3" },
   {input: "1, 2, 3" },
   // errors
   {input: "function f(x) { let y = 1, }" },
   {input: "function f(x=,) {" },
   {input: "{2,}" },
 ];
 
+const SINGLE_LINE_DATA = {
+  timestamp: null,
+  category: "devtools.main",
+  method: "execute_js",
+  object: "webconsole",
+  value: null,
+  extra: {
+    lines: "1"
+  }
+};
+
+const DATA = [
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  SINGLE_LINE_DATA,
+  {
+    timestamp: null,
+    category: "devtools.main",
+    method: "execute_js",
+    object: "webconsole",
+    value: null,
+    extra: {
+      lines: "3"
+    }
+  }
+];
+
 add_task(async function() {
+  // Let's reset the counts.
+  Services.telemetry.clearEvents();
+
+  // Ensure no events have been logged
+  const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
+  ok(!snapshot.parent, "No events have been logged for the main process");
+
   let hud = await openNewTabAndConsole(TEST_URI);
   let { inputNode } = hud.jsterm;
 
   for (let {input, shiftKey} of SHOULD_ENTER_MULTILINE) {
     hud.jsterm.setInputValue(input);
     EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
 
     let inputValue = hud.jsterm.getInputValue();
@@ -63,9 +104,36 @@ add_task(async function() {
 
     await waitFor(() => !hud.jsterm.getInputValue());
 
     let inputValue = hud.jsterm.getInputValue();
     is(inputNode.selectionStart, 0, "selection starts/ends at 0");
     is(inputNode.selectionEnd, 0, "selection starts/ends at 0");
     is(inputValue, "", "Input value is cleared");
   }
+
+  await hud.jsterm.execute("document.\nlocation.\nhref");
+
+  checkEventTelemetry();
 });
+
+function checkEventTelemetry() {
+  const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
+  const events = snapshot.parent.filter(event => event[1] === "devtools.main" &&
+                                                  event[2] === "execute_js" &&
+                                                  event[3] === "webconsole" &&
+                                                  event[4] === null
+  );
+
+  for (let i in DATA) {
+    const [ timestamp, category, method, object, value, extra ] = events[i];
+    const expected = DATA[i];
+
+    // ignore timestamp
+    ok(timestamp > 0, "timestamp is greater than 0");
+    is(category, expected.category, "category is correct");
+    is(method, expected.method, "method is correct");
+    is(object, expected.object, "object is correct");
+    is(value, expected.value, "value is correct");
+
+    is(extra.lines, expected.extra.lines, "lines is correct");
+  }
+}
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -296,8 +296,18 @@ devtools.main:
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has switched sidepanel tabs.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       oldpanel: The panel the user is switching from
       newpanel: The panel the user is switching to
+  execute_js:
+    objects: ["webconsole"]
+    bug_numbers: [1463083]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User has executed some JS in the Web Console.
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      lines: The number of lines contained in the command.