Bug 1378759 - Add a search input to each section r?chutten draft
authorflyingrub <flyinggrub@gmail.com>
Sat, 08 Jul 2017 19:46:36 +0200
changeset 607161 0d3674c42773174afe0e163c2ba0186c9b348195
parent 607160 f0974f1a2bd735b90c20f3873353252184ecc9dc
child 636962 8ce9e855fde6f12ba317ba270b4672ea645a7ba6
push id67915
push userbmo:flyinggrub@gmail.com
push dateTue, 11 Jul 2017 23:24:38 +0000
reviewerschutten
bugs1378759
milestone56.0a1
Bug 1378759 - Add a search input to each section r?chutten Also better integrate the process selector to the new design. MozReview-Commit-ID: 8YV6dYPFZvA
toolkit/content/aboutTelemetry.css
toolkit/content/aboutTelemetry.js
toolkit/content/aboutTelemetry.xhtml
toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
--- a/toolkit/content/aboutTelemetry.css
+++ b/toolkit/content/aboutTelemetry.css
@@ -39,31 +39,44 @@ body {
   flex-direction: column;
   font-size: 18px;
   color: var(--in-content-category-text);
   pointer-events: none;
   padding: 12px 21px 15px 21px;
   border-bottom: 1px solid var(--in-content-header-border-color);
 }
 
+.header {
+  display: flex;
+}
+
+.header select {
+  margin-left: 4px;
+}
+
+#sectionTitle {
+  flex-grow: 1;
+}
+
 .heading > h3 {
   margin: 0;
   padding-bottom: 12px;
 }
 
 #ping-type {
   align-self: center;
   pointer-events: all;
   cursor: pointer;
 }
 
 #older-ping, #newer-ping, #ping-date {
   pointer-events: all;
   -moz-user-select: none;
   cursor: pointer;
+  text-align: center;
 }
 
 .controls {
   display: flex;
   justify-content: space-between;
 }
 
 .category:not(.has-data) {
@@ -358,26 +371,23 @@ body[dir="rtl"] .copy-node {
 
 #raw-ping-data-section {
   width: 100%;
   height: 100%;
 }
 
 #raw-ping-data {
   margin: 0px;
+  font-size: 15px;
 }
 
 #hide-raw-ping {
   float: right;
   cursor: pointer;
   font-size: 20px;
   background-color:#d8d8d8;
   padding: 5px 10px;
 }
 
 caption {
   font-size: larger;
   margin: 5px 0;
-}
-
-.process-picker {
-  margin: 0 0.5em;
 }
\ No newline at end of file
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -321,25 +321,17 @@ var PingPicker = {
 
     document.addEventListener("click", (ev) => {
       if (ev.target.querySelector("#ping-picker")) {
         document.getElementById("ping-picker").classList.add("hidden");
       }
     });
     document.getElementById("choose-payload")
             .addEventListener("change", () => displayPingData(gPingData));
-    document.getElementById("scalars-processes")
-            .addEventListener("change", () => displayPingData(gPingData));
-    document.getElementById("keyed-scalars-processes")
-            .addEventListener("change", () => displayPingData(gPingData));
-    document.getElementById("histograms-processes")
-            .addEventListener("change", () => displayPingData(gPingData));
-    document.getElementById("keyed-histograms-processes")
-            .addEventListener("change", () => displayPingData(gPingData));
-    document.getElementById("events-processes")
+    document.getElementById("processes")
             .addEventListener("change", () => displayPingData(gPingData));
     Array.from(document.querySelectorAll(".change-ping")).forEach(el =>
       el.addEventListener("click", () =>
         document.getElementById("ping-picker").classList.remove("hidden"))
     );
   },
 
   onPingSourceChanged() {
@@ -707,16 +699,17 @@ var EnvironmentData = {
     let hasAddonData = Object.keys(ping.environment.addons).length > 0;
     let s = GenericSubsection.renderSubsectionHeader("addons", hasAddonData, "environment-data-section");
     s.appendChild(addonSection);
     dataDiv.appendChild(s);
   },
 
   appendRow(table, id, value) {
     let row = document.createElement("tr");
+    row.id = id;
     this.appendColumn(row, "td", id);
     this.appendColumn(row, "td", value);
     table.appendChild(row);
   },
   /**
    * Helper function for appending a column to the data table.
    *
    * @param aRowElement Parent row element
@@ -1357,26 +1350,23 @@ var Histogram = {
       // Add bucket label
       barDiv.appendChild(document.createTextNode(label));
 
       aDiv.appendChild(barDiv);
     }
 
     return text.substr(EOL.length); // Trim the EOL before the first line
   },
+};
 
-  /**
-   * Helper function for filtering histogram elements by their id
-   * Adds the "filter-blocked" class to histogram nodes whose IDs don't match the filter.
-   *
-   * @param aContainerNode Container node containing the histogram class nodes to filter
-   * @param aFilterText either text or /RegEx/. If text, case-insensitive and AND words
-   */
-  filterHistograms: function _filterHistograms(aContainerNode, aFilterText) {
-    let filter = aFilterText.toString();
+
+var Search = {
+
+  filterElements(elements, filterText) {
+    let filter = filterText.toString();
 
     // Pass if: all non-empty array items match (case-sensitive)
     function isPassText(subject, filter) {
       for (let item of filter) {
         if (item.length && subject.indexOf(item) < 0) {
           return false; // mismatch and not a spurious space
         }
       }
@@ -1400,39 +1390,46 @@ var Histogram = {
         filter = RegExp(r[1], r[2]);
       } catch (e) { // Incomplete or bad RegExp - always no match
         isPassFunc = function() {
           return false;
         };
       }
     }
 
-    let needLower = (isPassFunc === isPassText);
-
-    let histograms = aContainerNode.getElementsByClassName("histogram");
-    for (let hist of histograms) {
-      hist.classList[isPassFunc((needLower ? hist.id.toLowerCase() : hist.id), filter) ? "remove" : "add"]("filter-blocked");
+    let needLowerCase = (isPassFunc === isPassText);
+    for (let element of elements) {
+      let elementKey = needLowerCase ? element.id.toLowerCase() : element.id;
+      element.hidden = !isPassFunc(elementKey, filter);
     }
   },
 
-  /**
-   * Event handler for change at histograms filter input
-   *
-   * When invoked, 'this' is expected to be the filter HTML node.
-   */
-  histogramFilterChanged: function _histogramFilterChanged() {
+  searchHandler(e) {
     if (this.idleTimeout) {
       clearTimeout(this.idleTimeout);
     }
+    this.idleTimeout = setTimeout(() => Search.search(e.target.value), FILTER_IDLE_TIMEOUT);
+  },
 
-    this.idleTimeout = setTimeout( () => {
-      Histogram.filterHistograms(document.getElementById(this.getAttribute("target_id")), this.value);
-    }, FILTER_IDLE_TIMEOUT);
+  search(text) {
+    let selectedSection = document.querySelector(".data-section.active");
+    if (selectedSection.id.includes("histograms")) {
+      let histograms = selectedSection.getElementsByClassName("histogram");
+      this.filterElements(histograms, text);
+    } else if (selectedSection.id === "keyed-histograms-section") {
+      // let keyedHistograms = selectedSection.getElementsByClassName("keyed-histogram");
+      // this.filterElements(keyedHistograms, text);
+    } else {
+      let tables = selectedSection.querySelectorAll("table");
+      for (let table of tables) {
+        this.filterElements(table.rows, text);
+      }
+    }
   }
-};
+}
 
 /*
  * Helper function to render JS objects with white space between top level elements
  * so that they look better in the browser
  * @param   aObject JavaScript object or array to render
  * @return  String
  */
 function RenderObject(aObject) {
@@ -1572,16 +1569,17 @@ var GenericTable = {
            (typeof value == "object") &&
            (typeof value.valueOf() == "object")) {
           return RenderObject(value);
         }
         return value;
       });
 
       let newRow = document.createElement("tr");
+      newRow.id = row[0];
       table.appendChild(newRow);
 
       for (let i = 0; i < row.length; ++i) {
         let suffix = (i == (row.length - 1)) ? "\n" : "\t";
         let field = document.createElement("td");
         field.appendChild(document.createTextNode(row[i] + suffix));
         newRow.appendChild(field);
       }
@@ -1645,17 +1643,17 @@ var Scalars = {
   /**
    * Render the scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
     let scalarsSection = document.getElementById("scalars");
     removeAllChildNodes(scalarsSection);
 
-    let processesSelect = document.getElementById("scalars-processes");
+    let processesSelect = document.getElementById("processes");
     let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
 
     if (!aPayload.processes ||
         !selectedProcess ||
         !(selectedProcess in aPayload.processes)) {
       return;
     }
 
@@ -1679,17 +1677,17 @@ var KeyedScalars = {
   /**
    * Render the keyed scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
     let scalarsSection = document.getElementById("keyed-scalars");
     removeAllChildNodes(scalarsSection);
 
-    let processesSelect = document.getElementById("keyed-scalars-processes");
+    let processesSelect = document.getElementById("processes");
     let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
 
     if (!aPayload.processes ||
         !selectedProcess ||
         !(selectedProcess in aPayload.processes)) {
       return;
     }
 
@@ -1724,17 +1722,17 @@ var Events = {
   render(aPayload) {
     let eventsSection = document.getElementById("events");
     removeAllChildNodes(eventsSection);
 
     if (!aPayload.processes || !aPayload.processes.parent) {
       return;
     }
 
-    let processesSelect = document.getElementById("events-processes");
+    let processesSelect = document.getElementById("processes");
     let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
 
     if (!aPayload.processes ||
         !selectedProcess ||
         !(selectedProcess in aPayload.processes)) {
       return;
     }
 
@@ -1802,37 +1800,64 @@ function setupPageHeader() {
   let brandName = brandBundle.GetStringFromName("brandFullName");
   let subtitleText = bundle.formatStringFromName(
     "pageSubtitle", [serverOwner, brandName], 2);
 
   let subtitleElement = document.getElementById("page-subtitle");
   subtitleElement.appendChild(document.createTextNode(subtitleText));
 }
 
+function displayProcessesSelector(selectedSection) {
+  let whitelist = [
+    "scalars-section",
+    "keyed-scalars-section",
+    "histograms-section",
+    "keyed-histograms-section",
+    "events-section"
+  ];
+  let processes = document.getElementById("processes");
+  processes.hidden = !whitelist.includes(selectedSection);
+}
+
+function displaySearch(selectedSection) {
+  let blacklist = [
+    "home",
+  ];
+  let search = document.getElementById("search");
+  search.hidden = blacklist.includes(selectedSection);
+}
+
 /**
  * Change the section displayed
  */
 function show(selected) {
   let current_button = document.querySelector(".category.selected");
   current_button.classList.remove("selected");
   selected.classList.add("selected");
   // Hack because subsection text appear selected. See Bug 1375114.
   document.getSelection().empty();
 
+  let selectedValue = selected.getAttribute("value");
   let current_section = document.querySelector(".active");
-  let selected_section = document.getElementById(selected.getAttribute("value"));
+  let selected_section = document.getElementById(selectedValue);
   if (current_section == selected_section)
     return;
   current_section.classList.remove("active");
   current_section.hidden = true;
   selected_section.classList.add("active");
   selected_section.hidden = false;
 
-  let title = selected.querySelector(".category-name").textContent;
+  let title = selected.querySelector(".category-name").textContent.trim();
   document.getElementById("sectionTitle").textContent = title;
+
+  let search = document.getElementById("search");
+  let placeholder = bundle.formatStringFromName("filterPlaceholder", [ title ], 1);
+  search.setAttribute("placeholder", placeholder);
+  displayProcessesSelector(selectedValue);
+  displaySearch(selectedValue);
 }
 
 function showSubSection(selected) {
   let current_selection = document.querySelector(".category-subsection.selected");
   if (current_selection)
     current_selection.classList.remove("selected");
   selected.classList.add("selected");
 
@@ -1856,16 +1881,19 @@ function setupListeners() {
 
   let menu = document.getElementById("categories");
   menu.addEventListener("click", (e) => {
     if (e.target && e.target.parentNode == menu) {
       show(e.target)
     }
   });
 
+  let search = document.getElementById("search");
+  search.addEventListener("input", Search.searchHandler);
+
   // Clean up observers when page is closed
   window.addEventListener("unload",
     function(aEvent) {
       Settings.detachObservers();
   }, {once: true});
 
   document.getElementById("chrome-hangs-fetch-symbols").addEventListener("click",
     function() {
@@ -1992,17 +2020,17 @@ var LateWritesSingleton = {
 
 var HistogramSection = {
   render(aPayload) {
     let hgramDiv = document.getElementById("histograms");
     removeAllChildNodes(hgramDiv);
 
     let histograms = aPayload.histograms;
 
-    let hgramsSelect = document.getElementById("histograms-processes");
+    let hgramsSelect = document.getElementById("processes");
     let hgramsOption = hgramsSelect.selectedOptions.item(0);
     let hgramsProcess = hgramsOption.getAttribute("value");
     // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
     if (hgramsProcess === "parent") {
       hgramsProcess = "";
     }
     if (hgramsProcess &&
         "processes" in aPayload &&
@@ -2013,35 +2041,29 @@ var HistogramSection = {
     let hasData = Object.keys(histograms).length > 0;
     setHasData("histograms-section", hasData || hgramsSelect.options.length);
 
     if (hasData) {
       for (let [name, hgram] of Object.entries(histograms)) {
         Histogram.render(hgramDiv, name, hgram, {unpacked: true});
       }
 
-      let filterBox = document.getElementById("histograms-filter");
-      filterBox.addEventListener("input", Histogram.histogramFilterChanged);
-      if (filterBox.value.trim() != "") { // on load, no need to filter if empty
-        Histogram.filterHistograms(hgramDiv, filterBox.value);
-      }
-
       setHasData("histograms-section", true);
     }
   },
 }
 
 var KeyedHistogramSection = {
   render(aPayload) {
     let keyedDiv = document.getElementById("keyed-histograms");
     removeAllChildNodes(keyedDiv);
 
     let keyedHistograms = aPayload.keyedHistograms;
 
-    let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
+    let keyedHgramsSelect = document.getElementById("processes");
     let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
     let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
     // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
     if (keyedHgramsProcess === "parent") {
       keyedHgramsProcess = "";
     }
     if (keyedHgramsProcess &&
         "processes" in aPayload &&
@@ -2212,38 +2234,33 @@ function togglePingSections(isMainPing) 
       continue;
     }
     section.classList.toggle("has-data", isMainPing);
   }
 }
 
 function displayPingData(ping, updatePayloadList = false) {
   gPingData = ping;
-
   // Render raw ping data.
   RawPayload.render(ping);
 
   try {
     PingPicker.render();
     displayRichPingData(ping, updatePayloadList);
   } catch (err) {
     console.log(err);
     PingPicker._showRawPingData();
   }
 }
 
 function displayRichPingData(ping, updatePayloadList) {
   // Update the payload list and process lists
   if (updatePayloadList) {
     renderPayloadList(ping);
-    renderProcessList(ping, document.getElementById("scalars-processes"));
-    renderProcessList(ping, document.getElementById("keyed-scalars-processes"));
-    renderProcessList(ping, document.getElementById("histograms-processes"));
-    renderProcessList(ping, document.getElementById("keyed-histograms-processes"));
-    renderProcessList(ping, document.getElementById("events-processes"));
+    renderProcessList(ping, document.getElementById("processes"));
   }
 
   // Show general data.
   GeneralData.render(ping);
 
   // Show environment data.
   EnvironmentData.render(ping);
 
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -128,16 +128,18 @@
           <select id="choose-payload"></select>
         </div>
       </div>
 
       <div class="header">
           <div id="sectionTitle" class="header-name">
               &aboutTelemetry.pageTitle;
           </div>
+          <input type="text" id="search" placeholder="" hidden="true"/>
+          <select id="processes" hidden="true"></select>
       </div>
 
       <div id="home" class="tab active">
         <h3 id="page-subtitle"></h3>
         <p id="home-explanation"></p>
         <p id="ping-explanation"></p>
       </div>
 
@@ -160,58 +162,40 @@
       <section id="session-info-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
         <div id="session-info" class="data">
         </div>
       </section>
 
       <section id="scalars-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
-        <div class="processes-ui">
-          <select id="scalars-processes" class="process-picker"></select>
-        </div>
         <div id="scalars" class="data">
         </div>
       </section>
 
       <section id="keyed-scalars-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
-        <div class="processes-ui">
-          <select id="keyed-scalars-processes" class="process-picker"></select>
-        </div>
         <div id="keyed-scalars" class="data">
         </div>
       </section>
 
       <section id="histograms-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
-        <span class="filter-ui">
-          &aboutTelemetry.filterText; <input type="text" class="filter" id="histograms-filter" target_id="histograms"/>
-        </span>
-        <div class="processes-ui">
-          <select id="histograms-processes" class="process-picker"></select>
-        </div>
         <div id="histograms" class="data">
         </div>
       </section>
 
       <section id="keyed-histograms-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
-        <div class="processes-ui">
-          <select id="keyed-histograms-processes" class="process-picker"></select>
-        </div>
         <div id="keyed-histograms" class="data">
         </div>
       </section>
 
       <section id="events-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
-        <div class="processes-ui">
-          <select id="events-processes" class="process-picker"></select>
-        </div>
         <div id="events" class="data">
         </div>
       </section>
 
       <section id="simple-measurements-section" class="tab data-section expanded" hidden="true">
         <input type="checkbox" class="statebox"/>
         <div id="simple-measurements" class="data">
         </div>
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
@@ -28,16 +28,20 @@ telemetryPingTypeAll = all
 telemetryLogTitle = Telemetry Log
 
 telemetryLogHeadingId = Id
 
 telemetryLogHeadingTimestamp = Timestamp
 
 telemetryLogHeadingData = Data
 
+# Note to translators:
+# - %1$S will be replaced by the section name
+filterPlaceholder = Find in %1$S
+
 slowSqlMain = Slow SQL Statements on Main Thread
 
 slowSqlOther = Slow SQL Statements on Helper Threads
 
 slowSqlHits = Hits
 
 slowSqlAverage = Avg. Time (ms)