You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by iw...@apache.org on 2015/06/22 20:53:02 UTC

incubator-htrace git commit: HTRACE-194. gui: support multiple selections, zooming to fit a group of spans, deleting a group of spans (Colin Patrick McCabe via iwasakims)

Repository: incubator-htrace
Updated Branches:
  refs/heads/master 34b4c95f6 -> 7eee84045


HTRACE-194. gui: support multiple selections, zooming to fit a group of spans, deleting a group of spans (Colin Patrick McCabe via iwasakims)


Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/7eee8404
Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/7eee8404
Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/7eee8404

Branch: refs/heads/master
Commit: 7eee84045c23332254ef6b8370e271a766294d27
Parents: 34b4c95
Author: Masatake Iwasaki <iw...@apache.org>
Authored: Mon Jun 22 11:51:26 2015 -0700
Committer: Masatake Iwasaki <iw...@apache.org>
Committed: Mon Jun 22 11:51:26 2015 -0700

----------------------------------------------------------------------
 .../src/main/web/app/search_results_view.js     | 137 ++++++++----
 htrace-webapp/src/main/web/app/search_view.js   |  12 +-
 htrace-webapp/src/main/web/app/span_widget.js   | 215 +++++++++++--------
 3 files changed, 225 insertions(+), 139 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7eee8404/htrace-webapp/src/main/web/app/search_results_view.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/web/app/search_results_view.js b/htrace-webapp/src/main/web/app/search_results_view.js
index f7ece4b..3f009d4 100644
--- a/htrace-webapp/src/main/web/app/search_results_view.js
+++ b/htrace-webapp/src/main/web/app/search_results_view.js
@@ -60,7 +60,8 @@ htrace.SearchResultsView = Backbone.View.extend({
     this.widgetManager.handle({
       type: "mouseDown",
       x: this.getCanvasX(e),
-      y: this.getCanvasY(e)
+      y: this.getCanvasY(e),
+      raw: e
     });
     this.draw();
   },
@@ -70,7 +71,8 @@ htrace.SearchResultsView = Backbone.View.extend({
     this.widgetManager.handle({
       type: "mouseUp",
       x: this.getCanvasX(e),
-      y: this.getCanvasY(e)
+      y: this.getCanvasY(e),
+      raw: e
     });
     this.draw();
   },
@@ -88,7 +90,8 @@ htrace.SearchResultsView = Backbone.View.extend({
     this.widgetManager.handle({
       type: "mouseMove",
       x: this.getCanvasX(e),
-      y: this.getCanvasY(e)
+      y: this.getCanvasY(e),
+      raw: e
     });
     this.draw();
   },
@@ -256,6 +259,10 @@ htrace.SearchResultsView = Backbone.View.extend({
     $("#resultsCanvas").on("mousemove", function(e) {
       view.handleMouseMove(e);
     });
+    $("#resultsCanvas").off("contextmenu");
+    $("#resultsCanvas").on("contextmenu", function(e) {
+      return false;
+    });
   },
 
   remove: function() {
@@ -281,68 +288,117 @@ htrace.SearchResultsView = Backbone.View.extend({
       return null;
     }
     if (type === "begin") {
-      this.setBegin(d.valueOf());
+      this.setTimes({begin: d.valueOf()});
     } else if (type === "end") {
-      this.setEnd(d.valueOf());
+      this.setTimes({end: d.valueOf()});
     } else {
       throw "invalid type for handleBeginOrEndChange: expected begin or end.";
     }
+    this.render();
   },
 
-  setBegin: function(val) {
-    if (this.end < val + this.MINIMUM_TIME_SPAN) {
-      this.begin = val;
-      this.end = val + this.MINIMUM_TIME_SPAN;
-      console.log("SearchResultsView#setBegin(begin=" + this.begin +
-            ", end=" + this.end + ")");
-      $("#begin").val(htrace.dateToString(this.begin));
-      $("#end").val(htrace.dateToString(this.end));
-    } else {
-      this.begin = val;
-      console.log("SearchResultsView#setBegin(begin=" + this.begin + ")");
-      $("#begin").val(htrace.dateToString(this.begin));
+  setTimes: function(params) {
+    if (params["begin"]) {
+      this.begin = params["begin"];
     }
-    this.render();
+    if (params["end"]) {
+      this.end = params["end"];
+    }
+    if (this.end < this.begin) {
+      var b = this.begin;
+      this.begin = this.end;
+      this.end = b;
+    }
+    var delta = this.end - this.begin;
+    if (delta < this.MINIMUM_TIME_SPAN) {
+      var needed = this.MINIMUM_TIME_SPAN - delta;
+      this.begin -= (needed / 2);
+      this.end += (needed / 2);
+    }
+    $("#begin").val(htrace.dateToString(this.begin));
+    $("#end").val(htrace.dateToString(this.end));
+    // caller should invoke render()
   },
 
-  setEnd: function(val) {
-    if (this.begin + this.MINIMUM_TIME_SPAN > val) {
-      this.begin = val;
-      this.end = this.begin + this.MINIMUM_TIME_SPAN;
-      console.log("SearchResultsView#setEnd(begin=" + this.begin +
-            ", end=" + this.end + ")");
-      $("#begin").val(htrace.dateToString(this.begin));
-      $("#end").val(htrace.dateToString(this.end));
-    } else {
-      this.end = val;
-      console.log("SearchResultsView#setEnd(end=" + this.end + ")");
-      $("#end").val(htrace.dateToString(this.end));
+  clearHandler: function() {
+    console.log("invoking clearHandler.");
+    var toDelete = []
+    var noneSelected = true;
+    for (var i = 0; i < this.searchResults.length; i++) {
+      var resultSelected = false;
+      var model = this.searchResults.at(i);
+      htrace.treeTraverseDepthFirstPre(model,
+        htrace.getReifiedChildren, 0,
+          function(node, depth) {
+            if (noneSelected) {
+              if (node.get("selected")) {
+                resultSelected = true;
+              }
+            }
+          });
+      htrace.treeTraverseDepthFirstPre(model,
+        htrace.getReifiedParents, 0,
+          function(node, depth) {
+            if (node.get("selected")) {
+              resultSelected = true;
+            }
+          });
+      if (resultSelected) {
+        if (noneSelected) {
+          toDelete = [];
+          noneSelected = false;
+        }
+        toDelete.push(model);
+      } else if (noneSelected) {
+        toDelete.push(model);
+      }
     }
     this.render();
+    console.log("clearHandler: removing " + JSON.stringify(toDelete));
+    this.searchResults.remove(toDelete);
   },
 
-  zoomFitAll: function() {
-    var numResults = this.searchResults.size();
+  getSelectedSpansOrAllSpans: function() {
+    // Get the list of selected spans.
+    // If there are no spans selected, we return all spans.
+    var ret = [];
+    var noneSelected = true;
+    this.applyToAllSpans(function(span) {
+        if (span.get("selected")) {
+          if (noneSelected) {
+            ret = [];
+            noneSelected = false;
+          }
+          ret.push(span);
+        } else if (noneSelected) {
+          ret.push(span);
+        }
+      });
+    return ret;
+  },
+
+  zoomHandler: function() {
+    var zoomSpans = this.getSelectedSpansOrAllSpans();
+    var numResults = zoomSpans.length;
     if (numResults == 0) {
-      this.setBegin(0);
-      this.setEnd(this.MINIMUM_TIME_SPAN);
+      this.setTimes({begin:0, end:this.MINIMUM_TIME_SPAN});
+      this.render();
       return;
     }
     var minStart = 4503599627370496;
     var maxEnd = 0;
     for (var i = 0; i < numResults; i++) {
-      var span = this.searchResults.at(i);
-      var begin = span.getEarliestBegin();
+      var begin = zoomSpans[i].getEarliestBegin();
       if (begin < minStart) {
         minStart = begin;
       }
-      var end = span.getLatestEnd();
-      if (end > minStart) {
+      var end = zoomSpans[i].getLatestEnd();
+      if (end > maxEnd) {
         maxEnd = end;
       }
     }
-    this.setBegin(minStart);
-    this.setEnd(maxEnd);
+    this.setTimes({begin: minStart, end: maxEnd});
+    this.render();
   },
 
   // Apply a function to all spans
@@ -351,7 +407,6 @@ htrace.SearchResultsView = Backbone.View.extend({
       htrace.treeTraverseDepthFirstPre(this.searchResults.at(i),
         htrace.getReifiedChildren, 0,
           function(node, depth) {
-            console.log("node = " + node + ", node.constructor.name = " + node.constructor.name);
             cb(node);
           });
       htrace.treeTraverseDepthFirstPre(this.searchResults.at(i),

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7eee8404/htrace-webapp/src/main/web/app/search_view.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/web/app/search_view.js b/htrace-webapp/src/main/web/app/search_view.js
index 5144650..a530d70 100644
--- a/htrace-webapp/src/main/web/app/search_view.js
+++ b/htrace-webapp/src/main/web/app/search_view.js
@@ -36,21 +36,19 @@ htrace.SearchView = Backbone.View.extend({
     "click .add-field": "dropdownHandler",
     "blur #begin": "blurBeginHandler",
     "blur #end": "blurEndHandler",
-    "click #zoomButton": "zoomFitAllHandler"
+    "click #zoomButton": "zoomHandler"
   },
 
   searchHandler: function(e){
     e.preventDefault();
 
-    // Do a new search.
     this.doSearch(e.ctrlKey);
   },
 
   clearHandler: function(e){
     e.preventDefault();
 
-    // Clear existing search results.
-    this.searchResults.reset();
+    this.resultsView.clearHandler();
   },
 
   doSearch: function(showDebug){
@@ -105,7 +103,7 @@ htrace.SearchView = Backbone.View.extend({
         if (firstResults) {
           // After the initial search, zoom to fit everything.
           // On subsequent searches, we leave the viewport alone.
-          searchView.resultsView.zoomFitAll();
+          searchView.resultsView.zoomHandler();
         }
         searchView.searchInProgress = false;
         if (showDebug) {
@@ -168,9 +166,9 @@ htrace.SearchView = Backbone.View.extend({
     return this.resultsView.handleBeginOrEndChange(e, "end");
   },
 
-  zoomFitAllHandler: function(e) {
+  zoomHandler: function(e) {
     e.preventDefault();
-    this.resultsView.zoomFitAll();
+    this.resultsView.zoomHandler();
   },
 
   removePredicateView: function(predicateView) {

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/7eee8404/htrace-webapp/src/main/web/app/span_widget.js
----------------------------------------------------------------------
diff --git a/htrace-webapp/src/main/web/app/span_widget.js b/htrace-webapp/src/main/web/app/span_widget.js
index ad5ea07..e06c8b4 100644
--- a/htrace-webapp/src/main/web/app/span_widget.js
+++ b/htrace-webapp/src/main/web/app/span_widget.js
@@ -19,6 +19,94 @@
 
 var htrace = htrace || {};
 
+htrace.fillSpanDetailsView = function(span) {
+  var info = {
+    spanID: span.get("spanID"),
+    begin: htrace.dateToString(span.get("begin"), 10),
+    end: htrace.dateToString(span.get("end"), 10),
+    duration: ((span.get("end") - span.get("begin")) + " ms")
+  };
+  var explicitOrder = {
+    spanId: 1,
+    begin: 2,
+    end: 3,
+    duration: 4
+  };
+  keys = ["duration"];
+  for(k in span.attributes) {
+    if (k == "reifiedChildren") {
+      continue;
+    }
+    if (k == "reifiedParents") {
+      continue;
+    }
+    if (k == "selected") {
+      continue;
+    }
+    if (k == "timeAnnotations") {
+      // For timeline annotations, make the times into top-level keys.
+      var timeAnnotations = span.get("timeAnnotations");
+      for (var i = 0; i < timeAnnotations.length; i++) {
+        var key = htrace.dateToString(timeAnnotations[i].t);
+        keys.push(key);
+        info[key] = timeAnnotations[i].m;
+        explicitOrder[key] = 200;
+      }
+      continue;
+    }
+    if (k == "infoAnnotations") {
+      // For info annotations, move the keys to the top level.
+      // Surround them in brackets to make it clear that they are
+      // user-defined.
+      var infoAnnotations = span.get("infoAnnotations");
+      _.each(infoAnnotations, function(value, key) {
+        key = "[" + key + "]";
+        keys.push(key);
+        info[key] = value;
+        explicitOrder[key] = 200;
+      });
+      continue;
+    }
+    keys.push(k);
+    if (info[k] == null) {
+      info[k] = span.get(k);
+    }
+  }
+  // We sort the keys so that the stuff we want at the top appears at the top,
+  // and everything else is in alphabetical order.
+  keys = keys.sort(function(a, b) {
+      var oa = explicitOrder[a] || 100;
+      var ob = explicitOrder[b] || 100;
+      if (oa < ob) {
+        return -1;
+      } else if (oa > ob) {
+        return 1;
+      } else if (a < b) {
+        return -1;
+      } else if (a > b) {
+        return 1;
+      } else {
+        return 0;
+      }
+    });
+  var len = keys.length;
+  var h = '<table style="table-layout:fixed;width:100%;word-wrap:break-word">';
+  for (i = 0; i < len; i++) {
+    // Make every other row grey to improve visibility.
+    var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
+    h += _.template('<tr bgcolor="' + colorString + '">' +
+          '<td style="width:30%;word-wrap:break-word"><%- key %></td>' +
+          '<td style="width:70%;word-wrap:break-word"><%- val %></td>' +
+        "</tr>")({key: keys[i], val: info[keys[i]]});
+  }
+  h += '</table>';
+  $("#spanDetails").html(h);
+};
+
+htrace.clearSpanDetailsView = function() {
+  $("#spanDetails").html("");
+};
+
 // Widget containing the trace span displayed on the canvas.
 htrace.SpanWidget = function(params) {
   this.draw = function() {
@@ -113,90 +201,6 @@ htrace.SpanWidget = function(params) {
         (this.end - this.begin));
   };
 
-  this.fillSpanDetailsView = function() {
-    var info = {
-      spanID: this.span.get("spanID"),
-      begin: htrace.dateToString(this.span.get("begin"), 10),
-      end: htrace.dateToString(this.span.get("end"), 10),
-      duration: ((this.span.get("end") - this.span.get("begin")) + " ms")
-    };
-    var explicitOrder = {
-      spanId: 1,
-      begin: 2,
-      end: 3,
-      duration: 4
-    };
-    keys = ["duration"];
-    for(k in this.span.attributes) {
-      if (k == "reifiedChildren") {
-        continue;
-      }
-      if (k == "reifiedParents") {
-        continue;
-      }
-      if (k == "selected") {
-        continue;
-      }
-      if (k == "timeAnnotations") {
-        // For timeline annotations, make the times into top-level keys.
-        var timeAnnotations = this.span.get("timeAnnotations");
-        for (var i = 0; i < timeAnnotations.length; i++) {
-          var key = htrace.dateToString(timeAnnotations[i].t);
-          keys.push(key);
-          info[key] = timeAnnotations[i].m;
-          explicitOrder[key] = 200;
-        }
-        continue;
-      }
-      if (k == "infoAnnotations") {
-        // For info annotations, move the keys to the top level.
-        // Surround them in brackets to make it clear that they are
-        // user-defined.
-        var infoAnnotations = this.span.get("infoAnnotations");
-        _.each(infoAnnotations, function(value, key) {
-          key = "[" + key + "]";
-          keys.push(key);
-          info[key] = value;
-          explicitOrder[key] = 200;
-        });
-        continue;
-      }
-      keys.push(k);
-      if (info[k] == null) {
-        info[k] = this.span.get(k);
-      }
-    }
-    // We sort the keys so that the stuff we want at the top appears at the top,
-    // and everything else is in alphabetical order.
-    keys = keys.sort(function(a, b) {
-        var oa = explicitOrder[a] || 100;
-        var ob = explicitOrder[b] || 100;
-        if (oa < ob) {
-          return -1;
-        } else if (oa > ob) {
-          return 1;
-        } else if (a < b) {
-          return -1;
-        } else if (a > b) {
-          return 1;
-        } else {
-          return 0;
-        }
-      });
-    var len = keys.length;
-    var h = '<table style="table-layout:fixed;width:100%;word-wrap:break-word">';
-    for (i = 0; i < len; i++) {
-      // Make every other row grey to improve visibility.
-      var colorString = ((i%2) == 1) ? "#f1f1f1" : "#ffffff";
-      h += _.template('<tr bgcolor="' + colorString + '">' +
-            '<td style="width:30%;word-wrap:break-word"><%- key %></td>' +
-            '<td style="width:70%;word-wrap:break-word"><%- val %></td>' +
-          "</tr>")({key: keys[i], val: info[keys[i]]});
-    }
-    h += '</table>';
-    $("#spanDetails").html(h);
-  };
-
   this.handle = function(e) {
     switch (e.type) {
       case "mouseDown":
@@ -204,13 +208,42 @@ htrace.SpanWidget = function(params) {
               this.x0, this.xF, this.y0, this.yF)) {
           return true;
         }
-        this.manager.searchResultsView.applyToAllSpans(function(span) {
-            if (span.get("selected") == true) {
-              span.set("selected", false);
-            }
-          });
-        this.span.set("selected", true);
-        this.fillSpanDetailsView();
+        if (e.raw.ctrlKey) {
+          // If the control key is pressed, we can unselect the current
+          // selection, or create multiple selections.
+          if (this.span.get("selected")) {
+            this.span.set("selected", false);
+          } else {
+            this.span.set("selected", true);
+          }
+          var selection = null;
+          var multipleSelections = false;
+          this.manager.searchResultsView.applyToAllSpans(function(span) {
+              if (span.get("selected")) {
+                if (selection == null) {
+                  selection = span;
+                } else {
+                  multipleSelections = true;
+                }
+              }
+            });
+          if (multipleSelections) {
+            selection = null;
+          }
+          if (selection == null) {
+            htrace.clearSpanDetailsView();
+          } else {
+            htrace.fillSpanDetailsView(selection);
+          }
+        } else {
+          this.manager.searchResultsView.applyToAllSpans(function(span) {
+              if (span.get("selected")) {
+                span.set("selected", false);
+              }
+            });
+          this.span.set("selected", true);
+          htrace.fillSpanDetailsView(this.span);
+        }
         return true;
       case "draw":
         this.draw();