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 <[email protected]> Authored: Mon Jun 22 11:51:26 2015 -0700 Committer: Masatake Iwasaki <[email protected]> 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();
