Bug 1437446 : Make probe process choice more visible in about:telemetry r=chutten,flod
authorClément ALLAIN <clement.allain@ymail.com>
Wed, 08 May 2019 18:56:19 +0000
changeset 532068 bc29b6ed69e821515c82490a5c2afb5019d8f115
parent 532067 763d3da981174de750c7caf1d19bdff73c864f25
child 532069 44f3132aaed8ac51661a9cf5ba53b5c949f6d6b3
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten, flod
bugs1437446
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1437446 : Make probe process choice more visible in about:telemetry r=chutten,flod - Make process the second level menu of (Scalars, Keyed Scalars, Histograms, Keyed Histograms and Events) - Change the process dropdown to be a store dropdown - Main store is selected by default - Added a label before the store dropdown - Refactor a bit the code to avoid lot of duplications Note: This revision should be way cleaner Differential Revision: https://phabricator.services.mozilla.com/D29483
toolkit/content/aboutTelemetry.css
toolkit/content/aboutTelemetry.js
toolkit/content/aboutTelemetry.xhtml
toolkit/locales/en-US/toolkit/about/aboutTelemetry.ftl
--- a/toolkit/content/aboutTelemetry.css
+++ b/toolkit/content/aboutTelemetry.css
@@ -60,16 +60,27 @@ body:dir(rtl) #category-raw{
 .header select {
   margin-left: 4px;
 }
 
 #sectionTitle {
   flex-grow: 1;
 }
 
+#sectionFilters {
+  display: flex;
+  align-items: center;
+}
+
+#stores {
+  padding-top: 5px;
+  padding-left: 5px;
+  padding-bottom: 5px;
+}
+
 .heading > h3 {
   margin: 0;
   padding-bottom: 12px;
 }
 
 #ping-type {
   flex-grow: 1;
   text-align: center;
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -253,17 +253,17 @@ var PingPicker = {
     let pingPicker = document.getElementById("ping-picker");
     pingPicker.addEventListener("mouseenter", () => pingPickerNeedHide = false);
     pingPicker.addEventListener("mouseleave", () => pingPickerNeedHide = true);
     document.addEventListener("click", (ev) => {
       if (pingPickerNeedHide) {
         pingPicker.classList.add("hidden");
       }
     });
-    document.getElementById("processes")
+    document.getElementById("stores")
             .addEventListener("change", () => displayPingData(gPingData));
     Array.from(document.querySelectorAll(".change-ping")).forEach(el => {
       el.addEventListener("click", (event) => {
         if (!pingPicker.classList.contains("hidden")) {
           pingPicker.classList.add("hidden");
         } else {
           pingPicker.classList.remove("hidden");
           event.stopPropagation();
@@ -1398,16 +1398,22 @@ var Search = {
       if (!sectionHidden) {
         let sectionTitle = document.querySelector(`.category[value="${section.id}"] .category-name`).textContent;
         let sectionDataDiv = document.querySelector(`#${section.id}.has-data.active .data`);
         let titleDiv = document.createElement("h1");
         titleDiv.classList.add("data", "search-section-title");
         titleDiv.textContent = sectionTitle;
         section.insertBefore(titleDiv, sectionDataDiv);
         noSearchResults = false;
+      } else {
+        // Hide all subsections if the section is hidden
+        let subsections = section.querySelectorAll(".sub-section");
+        for (let subsection of subsections) {
+          subsection.hidden = true;
+        }
       }
     });
     this.updateNoResults(text, noSearchResults);
   },
 };
 
 /*
  * Helper function to render JS objects with white space between top level elements
@@ -1612,226 +1618,212 @@ var AddonDetails = {
       let table = GenericTable.render(explodeObject(addonDetails[provider]),
                                       headingStrings);
       table.appendChild(providerSection);
       addonSection.appendChild(table);
     }
   },
 };
 
-var Scalars = {
+class Section {
+  static renderContent(data, process, div, section) {
+    if (data && Object.keys(data).length > 0) {
+      let s = GenericSubsection.renderSubsectionHeader(process, true, section);
+      let heading = document.createElement("h2");
+      heading.textContent = process;
+      s.appendChild(heading);
+
+      this.renderData(data, s);
+
+      div.appendChild(s);
+      let separator = document.createElement("div");
+      separator.classList.add("clearfix");
+      div.appendChild(separator);
+    }
+  }
+
+  /**
+   * Make parent process the first one, content process the second
+   * then sort processes alphabetically
+   */
+  static processesComparator(a, b) {
+    if (a === "parent" || (a === "content" && b !== "parent")) {
+      return -1;
+    } else if (b === "parent" || b === "content") {
+      return 1;
+    } else if (a < b) {
+      return -1;
+    } else if (a > b) {
+      return 1;
+    }
+    return 0;
+  }
+
+  /**
+   * Render sections
+   */
+  static renderSection(divName, section, aPayload) {
+    let div = document.getElementById(divName);
+    removeAllChildNodes(div);
+
+    let data = {};
+    let hasData = false;
+    let selectedStore = getSelectedStore();
+
+    let payload = aPayload.stores;
+
+    let isCurrentPayload = !!payload;
+
+    // Sort processes
+    let sortedProcesses = isCurrentPayload
+      ? Object.keys(payload[selectedStore]).sort(this.processesComparator)
+      : Object.keys(aPayload.processes).sort(this.processesComparator);
+
+    // Render content by process
+    for (const process of sortedProcesses) {
+      data = isCurrentPayload
+        ? this.dataFiltering(payload, selectedStore, process)
+        : this.archivePingDataFiltering(aPayload, process);
+      hasData = hasData || data !== {};
+      this.renderContent(data, process, div, section, this.renderData);
+    }
+    setHasData(section, hasData);
+  }
+}
+
+class Scalars extends Section {
+  /**
+   * Return data from the current ping
+   */
+  static dataFiltering(payload, selectedStore, process) {
+    return payload[selectedStore][process].scalars;
+  }
+
+  /**
+   * Return data from an archived ping
+   */
+  static archivePingDataFiltering(payload, process) {
+    return payload.processes[process].scalars;
+  }
+
+  static renderData(data, div) {
+    const scalarsHeadings = ["about-telemetry-names-header", "about-telemetry-values-header"];
+    let scalarsTable = GenericTable.render(explodeObject(data), scalarsHeadings);
+    div.appendChild(scalarsTable);
+  }
+
   /**
    * 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("processes");
-    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
-
-    if (!selectedProcess) {
-      return;
-    }
+  static render(aPayload) {
+    const divName = "scalars";
+    const section = "scalars-section";
+    this.renderSection(divName, section, aPayload);
+  }
+}
 
-    let payload = aPayload.stores;
-    if (payload) { // Check for stores in the current ping data first
-      let hasData = false;
-      for (const store of Object.keys(payload)) {
-        if (!(selectedProcess in payload[store])) {
-          continue;
-        }
-
-        let scalars = payload[store][selectedProcess].scalars || {};
-        hasData = hasData || Array.from(processesSelect.options).some((option) => {
-          let value = option.getAttribute("value");
-          let sclrs = payload[store][value] && payload[store][value].scalars;
-          return sclrs && Object.keys(sclrs).length > 0;
-        });
-        if (Object.keys(scalars).length > 0) {
-          const headings = [
-            "about-telemetry-names-header",
-            "about-telemetry-values-header",
-          ];
+class KeyedScalars extends Section {
+  /**
+   * Return data from the current ping
+   */
+  static dataFiltering(payload, selectedStore, process) {
+    return payload[selectedStore][process].keyedScalars;
+  }
 
-          let s = GenericSubsection.renderSubsectionHeader(store, true, "scalars-section");
-          let table = GenericTable.render(explodeObject(scalars), headings);
-          let caption = document.createElement("caption");
-          caption.textContent = store;
-          table.appendChild(caption);
-          s.appendChild(table);
-          scalarsSection.appendChild(s);
-        }
-      }
-      setHasData("scalars-section", hasData);
-    } else { // Handle archived pings
-      if (!aPayload.processes ||
-        !(selectedProcess in aPayload.processes)) {
-        return;
-      }
+  /**
+   * Return data from an archived ping
+   */
+  static archivePingDataFiltering(payload, process) {
+    return payload.processes[process].keyedScalars;
+  }
 
-      let scalars = aPayload.processes[selectedProcess].scalars || {};
-      let hasData = Array.from(processesSelect.options).some((option) => {
-        let value = option.getAttribute("value");
-        let sclrs = aPayload.processes[value].scalars;
-        return sclrs && Object.keys(sclrs).length > 0;
-      });
+  static renderData(data, div) {
+    const scalarsHeadings = ["about-telemetry-names-header", "about-telemetry-values-header"];
+    for (let scalarId in data) {
+      // Add the name of the scalar.
+      let container = document.createElement("div");
+      container.classList.add("keyed-scalar");
+      container.id = scalarId;
+      let scalarNameSection = document.createElement("p");
+      scalarNameSection.classList.add("keyed-title");
+      scalarNameSection.appendChild(document.createTextNode(scalarId));
+      container.appendChild(scalarNameSection);
+      // Populate the section with the key-value pairs from the scalar.
+      const table = GenericTable.render(explodeObject(data[scalarId]), scalarsHeadings);
+      container.appendChild(table);
+      div.appendChild(container);
+    }
+  }
 
-      setHasData("scalars-section", hasData);
-      if (Object.keys(scalars).length > 0) {
-        const headings = [
-          "about-telemetry-names-header",
-          "about-telemetry-values-header",
-        ];
-        const table = GenericTable.render(explodeObject(scalars), headings);
-        scalarsSection.appendChild(table);
-      }
-    }
-  },
-};
-
-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("processes");
-    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
-
-    if (!selectedProcess) {
-      return;
-    }
-
-    let payload = aPayload.stores;
-    if (payload) { // Check for stores in the current ping data first
-      let hasData = false;
-      for (const store of Object.keys(payload)) {
-        if (!(selectedProcess in payload[store])) {
-          continue;
-        }
-
-        let keyedScalars = payload[store][selectedProcess].keyedScalars || {};
-        hasData = hasData || Array.from(processesSelect.options).some((option) => {
-          let value = option.getAttribute("value");
-          let keyedS = payload[store][value] && payload[store][value].keyedScalars;
-          return keyedS && Object.keys(keyedS).length > 0;
-        });
-        if (!Object.keys(keyedScalars).length > 0) {
-          continue;
-        }
-
-        let s = GenericSubsection.renderSubsectionHeader(store, true, "keyed-scalars-section");
-        let heading = document.createElement("h2");
-        heading.textContent = store;
-        s.appendChild(heading);
-
-        const headings = [
-          "about-telemetry-names-header",
-          "about-telemetry-values-header",
-        ];
-        for (let scalar in keyedScalars) {
-          // Add the name of the scalar.
-          let container = document.createElement("div");
-          container.classList.add("keyed-scalar");
-          container.id = scalar;
-          let scalarNameSection = document.createElement("p");
-          scalarNameSection.classList.add("keyed-title");
-          scalarNameSection.appendChild(document.createTextNode(scalar));
-          container.appendChild(scalarNameSection);
-          // Populate the section with the key-value pairs from the scalar.
-          const table = GenericTable.render(explodeObject(keyedScalars[scalar]), headings);
-          container.appendChild(table);
-          s.appendChild(container);
-        }
-
-        scalarsSection.appendChild(s);
-      }
-      setHasData("keyed-scalars-section", hasData);
-    } else { // Handle archived pings
-      if (!aPayload.processes ||
-        !(selectedProcess in aPayload.processes)) {
-        return;
-      }
-
-      let keyedScalars = aPayload.processes[selectedProcess].keyedScalars || {};
-      let hasData = Array.from(processesSelect.options).some((option) => {
-        let value = option.getAttribute("value");
-        let keyedS = aPayload.processes[value].keyedScalars;
-        return keyedS && Object.keys(keyedS).length > 0;
-      });
-
-      setHasData("keyed-scalars-section", hasData);
-      if (!Object.keys(keyedScalars).length > 0) {
-        return;
-      }
-
-      const headings = [
-        "about-telemetry-names-header",
-        "about-telemetry-values-header",
-      ];
-      for (let scalar in keyedScalars) {
-        // Add the name of the scalar.
-        let container = document.createElement("div");
-        container.classList.add("keyed-scalar");
-        container.id = scalar;
-        let scalarNameSection = document.createElement("p");
-        scalarNameSection.classList.add("keyed-title");
-        scalarNameSection.appendChild(document.createTextNode(scalar));
-        container.appendChild(scalarNameSection);
-        // Populate the section with the key-value pairs from the scalar.
-        const table = GenericTable.render(explodeObject(keyedScalars[scalar]), headings);
-        container.appendChild(table);
-        scalarsSection.appendChild(container);
-      }
-    }
-  },
-};
+  static render(aPayload) {
+    const divName = "keyed-scalars";
+    const section = "keyed-scalars-section";
+    this.renderSection(divName, section, aPayload);
+  }
+}
 
 var Events = {
   /**
    * Render the event data - if present - from the payload in a simple table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
-    let eventsSection = document.getElementById("events");
-    removeAllChildNodes(eventsSection);
-
-    let processesSelect = document.getElementById("processes");
-    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
-
-    if (!aPayload.processes ||
-        !selectedProcess ||
-        !(selectedProcess in aPayload.processes)) {
-      return;
+    let eventsDiv = document.getElementById("events");
+    removeAllChildNodes(eventsDiv);
+    const headings = [
+      "about-telemetry-time-stamp-header",
+      "about-telemetry-category-header",
+      "about-telemetry-method-header",
+      "about-telemetry-object-header",
+      "about-telemetry-values-header",
+      "about-telemetry-extra-header",
+    ];
+    let payload = aPayload.processes;
+    let hasData = false;
+    if (payload) {
+      for (const process of Object.keys(aPayload.processes)) {
+        let data = aPayload.processes[process].events;
+        hasData = hasData || data !== {};
+        if (data && Object.keys(data).length > 0) {
+          let s = GenericSubsection.renderSubsectionHeader(process, true, "events-section");
+          let heading = document.createElement("h2");
+          heading.textContent = process;
+          s.appendChild(heading);
+          const table = GenericTable.render(data, headings);
+          s.appendChild(table);
+          eventsDiv.appendChild(s);
+          let separator = document.createElement("div");
+          separator.classList.add("clearfix");
+          eventsDiv.appendChild(separator);
+        }
+      }
+    } else {
+      // handle archived ping
+      for (const process of Object.keys(aPayload.events)) {
+        let data = process;
+        hasData = hasData || data !== {};
+        if (data && Object.keys(data).length > 0) {
+          let s = GenericSubsection.renderSubsectionHeader(process, true, "events-section");
+          let heading = document.createElement("h2");
+          heading.textContent = process;
+          s.appendChild(heading);
+          const table = GenericTable.render(data, headings);
+          eventsDiv.appendChild(table);
+          let separator = document.createElement("div");
+          separator.classList.add("clearfix");
+          eventsDiv.appendChild(separator);
+        }
+      }
     }
-
-    let events = aPayload.processes[selectedProcess].events || {};
-    let hasData = Array.from(processesSelect.options).some((option) => {
-      let value = option.getAttribute("value");
-      let evts = aPayload.processes[value].events;
-      return evts && Object.keys(evts).length > 0;
-    });
     setHasData("events-section", hasData);
-    if (Object.keys(events).length > 0) {
-      const headings = [
-        "about-telemetry-time-stamp-header",
-        "about-telemetry-category-header",
-        "about-telemetry-method-header",
-        "about-telemetry-object-header",
-        "about-telemetry-values-header",
-        "about-telemetry-extra-header",
-      ];
-      const table = GenericTable.render(events, headings);
-      eventsSection.appendChild(table);
-    }
   },
 };
 
 var Origins = {
   render(aOrigins) {
     let originSection = document.getElementById("origins");
     removeAllChildNodes(originSection);
 
@@ -1882,27 +1874,27 @@ function setupServerOwnerBranding() {
     [document.getElementById("page-subtitle"), "about-telemetry-page-subtitle"],
     [document.getElementById("origins-explanation"), "about-telemetry-origins-explanation"],
   ];
   for (const [elt, l10nName] of elements) {
     document.l10n.setAttributes(elt, l10nName, {telemetryServerOwner: serverOwner});
   }
 }
 
-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);
-}
+/**
+ * Display the store selector if we are on one
+ * of the whitelisted sections
+ */
+function displayStoresSelector(selectedSection) {
+    let whitelist = ["scalars-section", "keyed-scalars-section", "histograms-section", "keyed-histograms-section"];
+    let stores = document.getElementById("stores");
+    stores.hidden = !whitelist.includes(selectedSection);
+    let storesLabel = document.getElementById("storesLabel");
+    storesLabel.hidden = !whitelist.includes(selectedSection);
+  }
 
 function refreshSearch() {
   removeSearchSectionTitles();
   let selectedSection = document.querySelector(".category.selected").getAttribute("value");
   let search = document.getElementById("search");
   if (!Search.blacklist.includes(selectedSection)) {
     Search.search(search.value);
   }
@@ -2014,17 +2006,17 @@ function show(selected) {
   selected.classList.add("selected");
 
   document.querySelectorAll("section").forEach((section) => {
     section.classList.remove("active");
   });
   selected_section.classList.add("active");
 
   adjustHeaderState();
-  displayProcessesSelector(selectedValue);
+  displayStoresSelector(selectedValue);
   adjustSearchState();
   changeUrlPath(selectedValue);
 }
 
 function showSubSection(selected) {
   if (!selected) {
     return;
   }
@@ -2195,152 +2187,77 @@ var LateWritesSingleton = {
 
     let stacks = lateWrites.stacks;
     let memoryMap = lateWrites.memoryMap;
     StackRenderer.renderStacks("late-writes", stacks, memoryMap,
                                LateWritesSingleton.renderHeader);
   },
 };
 
-var HistogramSection = {
-  render(aPayload) {
-    let hgramDiv = document.getElementById("histograms");
-    removeAllChildNodes(hgramDiv);
-
-    let histograms = {};
-    let hgramsSelect = document.getElementById("processes");
-    let hgramsOption = hgramsSelect.selectedOptions.item(0);
-    let hgramsProcess = hgramsOption.getAttribute("value");
-
-    let payload = aPayload.stores;
-    if (payload) { // Check for stores in the current ping data first
-      let hasData = false;
-      for (const store of Object.keys(payload)) {
-        if (store in payload && hgramsProcess in payload[store] &&
-          "histograms" in payload[store][hgramsProcess]) {
-          histograms = payload[store][hgramsProcess].histograms;
-        }
-
-        hasData = hasData || Array.from(hgramsSelect.options).some((option) => {
-          let value = option.getAttribute("value");
-          let histos = payload[store][value].histograms;
-          return histos && Object.keys(histos).length > 0;
-        });
+class HistogramSection extends Section {
+  /**
+   * Return data from the current ping
+   */
+  static dataFiltering(payload, selectedStore, process) {
+    return payload[selectedStore][process].histograms;
+  }
 
-        if (Object.keys(histograms).length > 0) {
-          let s = GenericSubsection.renderSubsectionHeader(store, true, "histograms-section");
-          let heading = document.createElement("h2");
-          heading.textContent = store;
-          s.appendChild(heading);
-          for (let [name, hgram] of Object.entries(histograms)) {
-            Histogram.render(s, name, hgram, {unpacked: true});
-          }
-          hgramDiv.appendChild(s);
-          let separator = document.createElement("div");
-          separator.classList.add("clearfix");
-          hgramDiv.appendChild(separator);
-        }
-      }
+  /**
+   * Return data from an archived ping
+   */
+  static archivePingDataFiltering(payload, process) {
+    if (process === "parent") {
+      return payload.histograms;
+    }
+    return payload.processes[process].histograms;
+  }
 
-      setHasData("histograms-section", hasData);
-    } else { // Handle archived pings
-      if (hgramsProcess === "parent") {
-        histograms = aPayload.histograms;
-      } else if ("processes" in aPayload && hgramsProcess in aPayload.processes &&
-        "histograms" in aPayload.processes[hgramsProcess]) {
-        histograms = aPayload.processes[hgramsProcess].histograms;
-      }
+  static renderData(data, div) {
+    for (let [hName, hgram] of Object.entries(data)) {
+      Histogram.render(div, hName, hgram, {unpacked: true});
+    }
+  }
 
-      let hasData = Array.from(hgramsSelect.options).some((option) => {
-        let value = option.getAttribute("value");
-        if (value == "parent") {
-          return Object.keys(aPayload.histograms).length > 0;
-        }
-        let histos = aPayload.processes[value].histograms;
-        return histos && Object.keys(histos).length > 0;
-      });
-      setHasData("histograms-section", hasData);
-
-      if (Object.keys(histograms).length > 0) {
-        for (let [name, hgram] of Object.entries(histograms)) {
-          Histogram.render(hgramDiv, name, hgram, {unpacked: true});
-        }
-      }
-    }
-  },
-};
+  static render(aPayload) {
+    const divName = "histograms";
+    const section = "histograms-section";
+    this.renderSection(divName, section, aPayload);
+  }
+}
 
-var KeyedHistogramSection = {
-  render(aPayload) {
-    let keyedDiv = document.getElementById("keyed-histograms");
-    removeAllChildNodes(keyedDiv);
-
-    let keyedHistograms = {};
-    let keyedHgramsSelect = document.getElementById("processes");
-    let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
-    let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
-
-    let payload = aPayload.stores;
-    if (payload) { // Check for stores in the current ping data first
-      let hasData = false;
-      for (const store of Object.keys(payload)) {
-        if (store in payload && keyedHgramsProcess in payload[store] &&
-          "keyedHistograms" in payload[store][keyedHgramsProcess]) {
-          keyedHistograms = payload[store][keyedHgramsProcess].keyedHistograms;
-        }
-
-        hasData = hasData || Array.from(keyedHgramsSelect.options).some((option) => {
-          let value = option.getAttribute("value");
-          let keyedHistos = payload[store][value].keyedHistograms;
-          return keyedHistos && Object.keys(keyedHistos).length > 0;
-        });
+class KeyedHistogramSection extends Section {
+  /**
+   * Return data from the current ping
+   */
+  static dataFiltering(payload, selectedStore, process) {
+    return payload[selectedStore][process].keyedHistograms;
+  }
 
-        if (Object.keys(keyedHistograms).length > 0) {
-          let s = GenericSubsection.renderSubsectionHeader(store, true, "keyed-histograms-section");
-          let heading = document.createElement("h2");
-          heading.textContent = store;
-          s.appendChild(heading);
-          for (let [id, keyed] of Object.entries(keyedHistograms)) {
-            KeyedHistogram.render(s, id, keyed, {unpacked: true});
-          }
-          keyedDiv.appendChild(s);
-          let separator = document.createElement("div");
-          separator.classList.add("clearfix");
-          keyedDiv.appendChild(separator);
-        }
-      }
+  /**
+   * Return data from an archived ping
+   */
+  static archivePingDataFiltering(payload, process) {
+    if (process === "parent") {
+      return payload.keyedHistograms;
+    }
+    return payload.processes[process].keyedHistograms;
+  }
 
-      setHasData("keyed-histograms-section", hasData);
-    } else { // Handle archived pings
-      if (keyedHgramsProcess === "parent") {
-        keyedHistograms = aPayload.keyedHistograms;
-      } else if ("processes" in aPayload && keyedHgramsProcess in aPayload.processes &&
-        "keyedHistograms" in aPayload.processes[keyedHgramsProcess]) {
-        keyedHistograms = aPayload.processes[keyedHgramsProcess].keyedHistograms;
-      }
+  static renderData(data, div) {
+    for (let [id, keyed] of Object.entries(data)) {
+      KeyedHistogram.render(div, id, keyed, {unpacked: true});
+    }
+  }
 
-      let hasData = Array.from(keyedHgramsSelect.options).some((option) => {
-        let value = option.getAttribute("value");
-        if (value == "parent") {
-          return Object.keys(aPayload.keyedHistograms).length > 0;
-        }
-        let keyedHistos = aPayload.processes[value].keyedHistograms;
-        return keyedHistos && Object.keys(keyedHistos).length > 0;
-      });
-      setHasData("keyed-histograms-section", hasData);
-      if (Object.keys(keyedHistograms).length > 0) {
-        for (let [id, keyed] of Object.entries(keyedHistograms)) {
-          if (Object.keys(keyed).length > 0) {
-            KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
-          }
-        }
-      }
-    }
-  },
-};
+  static render(aPayload) {
+    const divName = "keyed-histograms";
+    const section = "keyed-histograms-section";
+    this.renderSection(divName, section, aPayload);
+  }
+}
 
 var SessionInformation = {
   render(aPayload) {
     let infoSection = document.getElementById("session-info");
     removeAllChildNodes(infoSection);
 
     let hasData = Object.keys(aPayload.info).length > 0;
     setHasData("session-info-section", hasData);
@@ -2408,41 +2325,54 @@ var SimpleMeasurements = {
     for (let key of sortedKeys) {
       result[key] = aSimpleMeasurements[key];
     }
 
     return result;
   },
 };
 
-function renderProcessList(payload, selectEl) {
-  removeAllChildNodes(selectEl);
-  let option = document.createElement("option");
-  option.appendChild(document.createTextNode("parent"));
-  option.setAttribute("value", "parent");
-  option.selected = true;
-  selectEl.appendChild(option);
+/**
+ * Render stores options
+ */
+function renderStoreList(payload) {
+    let storeSelect = document.getElementById("stores");
+    let storesLabel = document.getElementById("storesLabel");
+    removeAllChildNodes(storeSelect);
+
+    if (!("stores" in payload)) {
+      storeSelect.classList.add("hidden");
+      storesLabel.classList.add("hidden");
+      return;
+    }
+
+    storeSelect.classList.remove("hidden");
+    storesLabel.classList.remove("hidden");
+    storeSelect.disabled = false;
 
-  if (!("processes" in payload)) {
-    selectEl.disabled = true;
-    return;
-  }
-  selectEl.disabled = false;
+    for (let store of Object.keys(payload.stores)) {
+      let option = document.createElement("option");
+      option.appendChild(document.createTextNode(store));
+      option.setAttribute("value", store);
+      // Select main store by default
+      if (store === "main") {
+        option.selected = true;
+      }
+      storeSelect.appendChild(option);
+    }
+}
 
-  for (let process of Object.keys(payload.processes)) {
-    // TODO: parent hgrams are on root payload, not in payload.processes.parent
-    // When/If that gets moved, you'll need to remove this
-    if (process === "parent") {
-      continue;
-    }
-    option = document.createElement("option");
-    option.appendChild(document.createTextNode(process));
-    option.setAttribute("value", process);
-    selectEl.appendChild(option);
-  }
+/**
+ * Return the selected store
+ */
+function getSelectedStore() {
+  let storeSelect = document.getElementById("stores");
+  let storeSelectedOption = storeSelect.selectedOptions.item(0);
+  let selectedStore = storeSelectedOption !== null ? storeSelectedOption.getAttribute("value") : undefined;
+  return selectedStore;
 }
 
 function togglePingSections(isMainPing) {
   // We always show the sections that are "common" to all pings.
   let commonSections = new Set(["heading",
                                 "home-section",
                                 "general-data-section",
                                 "environment-data-section",
@@ -2471,19 +2401,19 @@ function displayPingData(ping, updatePay
     refreshSearch();
   } catch (err) {
     console.log(err);
     PingPicker._showRawPingData();
   }
 }
 
 function displayRichPingData(ping, updatePayloadList) {
-  // Update the payload list and process lists
+  // Update the payload list and store lists
   if (updatePayloadList) {
-    renderProcessList(ping.payload, document.getElementById("processes"));
+    renderStoreList(ping.payload);
   }
 
   // Show general data.
   GeneralData.render(ping);
 
   // Show environment data.
   EnvironmentData.render(ping);
 
@@ -2500,19 +2430,19 @@ function displayRichPingData(ping, updat
     // Ensure we always have at least the parent process.
     let payload = { processes: { parent: {} } };
     for (let process of Object.keys(ping.payload.events)) {
       payload.processes[process] = {
         events: ping.payload.events[process],
       };
     }
 
-    // We transformed the actual payload, let's reload the process list if necessary.
+    // We transformed the actual payload, let's reload the store list if necessary.
     if (updatePayloadList) {
-      renderProcessList(payload, document.getElementById("processes"));
+      renderStoreList(payload);
     }
 
     // Show event data.
     Events.render(payload);
     return;
   }
 
 
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -115,20 +115,22 @@
               <optgroup data-l10n-id="about-telemetry-option-group-older">
               </optgroup>
             </select>
           </div>
         </div>
       </div>
 
       <div class="header">
-          <div id="sectionTitle" class="header-name" data-l10n-id="about-telemetry-page-title">
-          </div>
-          <select id="processes" hidden="true"></select>
-          <input type="text" id="search" placeholder=""/>
+        <div id="sectionTitle" class="header-name" data-l10n-id="about-telemetry-page-title" />
+        <div id="sectionFilters">
+          <label id="storesLabel" for="stores" hidden="true" data-l10n-id="about-telemetry-current-store" />
+          <select id="stores" hidden="true"></select>
+          <input type="text" id="search" placeholder="" />
+        </div>
       </div>
 
       <div id="no-search-results" hidden="true" class="hidden">
         <span id="no-search-results-text"></span>
         <div class="no-search-results-image"></div>
       </div>
 
       <section id="home-section" class="active">
--- a/toolkit/locales/en-US/toolkit/about/aboutTelemetry.ftl
+++ b/toolkit/locales/en-US/toolkit/about/aboutTelemetry.ftl
@@ -10,16 +10,17 @@ about-telemetry-choose-ping = Choose pin
 about-telemetry-archive-ping-type = Ping Type
 about-telemetry-archive-ping-header = Ping
 about-telemetry-option-group-today = Today
 about-telemetry-option-group-yesterday = Yesterday
 about-telemetry-option-group-older = Older
 about-telemetry-previous-ping = <<
 about-telemetry-next-ping = >>
 about-telemetry-page-title = Telemetry Data
+about-telemetry-current-store = Current Store: 
 about-telemetry-more-information = Looking for more information?
 about-telemetry-firefox-data-doc = The <a data-l10n-name="data-doc-link">Firefox Data Documentation</a> contains guides about how to work with our data tools.
 about-telemetry-telemetry-client-doc = The <a data-l10n-name="client-doc-link">Firefox Telemetry client documentation</a> includes definitions for concepts, API documentation and data references.
 about-telemetry-telemetry-dashboard = The <a data-l10n-name="dashboard-link">Telemetry dashboards</a> allow you to visualize the data Mozilla receives via Telemetry.
 about-telemetry-telemetry-probe-dictionary = The <a data-l10n-name="probe-dictionary-link">Probe Dictionary</a> provides details and descriptions for the probes collected by Telemetry.
 about-telemetry-show-in-Firefox-json-viewer = Open in the JSON viewer
 about-telemetry-home-section = Home
 about-telemetry-general-data-section = General Data