1
0
Fork 0

First draft to display the result of explain on a query. Not yet beutiful

This commit is contained in:
Michael Hackstein 2014-12-04 09:53:57 +00:00
parent 0d8bf44215
commit 3f5e1625bd
5 changed files with 549 additions and 314 deletions

View File

@ -25,6 +25,7 @@
<div id="queryContent">
<ul class="arango-tab">
<li><a href="#result" data-toggle="pill" id="result-switch">Result</a></li>
<li><a href="#explain" data-toggle="pill" id="explain-switch">Explain</a></li>
<li><a href="#query" data-toggle="pill" id="query-switch">Query</a></li>
<li><a href="#customs" data-toggle="pill" id="customs-switch">My Queries</a></li>
</ul>
@ -60,6 +61,7 @@
<div>
<button id="submitQueryButton" class="button-success query-button">Submit</button>
<button id="explainQueryButton" class="button-success query-button">Explain</button>
<button id="clearQueryButton" class="button-close query-button">Clear</button>
<div class="styled-select">
<select id="querySelect" class="query-select"/>
@ -74,13 +76,30 @@
</div>
</div>
<div class="tab-content" id="tabContentExplain">
<div class="tab-pane" id="explain">
<div id="editorLabel" class="editor-label"></div>
<div id="outputToolbar" class="query-toolbar output-toolbar">
</div>
<div class="explain-warnings"></div>
<div class="contentDiv query-output">
<svg id="explainOutput" class="explain-tree"></svg>
</div>
</div>
</div>
<div class="tab-content" id="tabContentResult">
<div class="tab-pane" id="result">
<div id="editorLabel" class="editor-label"></div>
<div id="outputToolbar" class="query-toolbar output-toolbar">
<!--
<span class="icon_arangodb icon_arangodb_trash queryTooltips tooltip-margin" title="Clear" id="clearOutput"></span>
<span class="icon_arangodb icon_arangodb_arrow1 queryTooltips" title="Unfold" id="bigOutput"></span>
-->
</div>
<div id="queryOutput" class="contentDiv query-output">
</div>

View File

@ -0,0 +1,11 @@
<script id="warningList.ejs" type="text/template">
<% if (warnings.length > 0) { %>
<div>
<ul>
<% console.log(warnings); _.each(warnings, function(w) { console.log(w);%>
<li><b><%=w.code%></b>: <%=w.message%></li>
<% }); %>
</ul>
</div>
<% } %>
</script>

View File

@ -1,15 +1,16 @@
/*jshint browser: true */
/*jshint unused: false */
/*global require, exports, Backbone, EJS, $, setTimeout, localStorage, ace, Storage, window, _ */
/*global _, arangoHelper, templateEngine, jQuery, Joi*/
/*global _, arangoHelper, templateEngine, jQuery, Joi, d3*/
(function () {
"use strict";
window.queryView = Backbone.View.extend({
el: '#content',
id: '#customsDiv',
warningTemplate: templateEngine.createTemplate("warningList.ejs"),
tabArray: [],
cachedQuery: "",
execPending: false,
initialize: function () {
this.getAQL();
@ -21,8 +22,9 @@
"click #result-switch": "switchTab",
"click #query-switch": "switchTab",
'click #customs-switch': "switchTab",
'click #submitQueryIcon': 'submitQuery',
'click #explain-switch': "switchTab",
'click #submitQueryButton': 'submitQuery',
'click #explainQueryButton': 'explainQuery',
'click #commentText': 'commentText',
'click #uncommentText': 'uncommentText',
'click #undoText': 'undoText',
@ -77,7 +79,7 @@
updateTable: function () {
this.tableDescription.rows = this.customQueries;
_.each(this.tableDescription.rows, function(k, v) {
_.each(this.tableDescription.rows, function(k) {
k.thirdRow = '<a class="deleteButton"><span class="icon_arangodb_roundminus"' +
' title="Delete query"></span></a>';
});
@ -99,7 +101,7 @@
initTabArray: function() {
var self = this;
$(".arango-tab").children().each( function(index) {
$(".arango-tab").children().each( function() {
self.tabArray.push($(this).children().first().attr("id"));
});
},
@ -586,35 +588,178 @@
*/
},
submitQuery: function () {
var self = this;
readQueryData: function() {
var inputEditor = ace.edit("aqlEditor");
var selectedText = inputEditor.session.getTextRange(inputEditor.getSelectionRange());
var sizeBox = $('#querySize');
var data = {
query: selectedText || inputEditor.getValue(),
batchSize: parseInt(sizeBox.val(), 10),
id: "currentFrontendQuery"
};
var outputEditor = ace.edit("queryOutput");
return JSON.stringify(data);
},
followQueryPath: function(root, nodes) {
var known = {};
known[nodes[0].id] = root;
var i, nodeData, j, dep;
for (i = 1; i < nodes.length; ++i) {
nodeData = this.preparePlanNodeEntry(nodes[i]);
known[nodes[i].id] = nodeData;
dep = nodes[i].dependencies;
for (j = 0; j < dep.length; ++j) {
known[dep[j]].children.push(nodeData);
}
}
},
preparePlanNodeEntry: function(node) {
var json = {
estimatedCost: node.estimatedCost,
estimatedNrItems: node.estimatedNrItems,
type: node.type,
children: []
};
switch (node.type) {
case "SubqueryNode":
this.followQueryPath(json, node.subquery.nodes);
break;
default:
}
return json;
},
drawTree: function(treeData) {
// outputEditor.setValue(JSON.stringify(treeData, undefined, 2));
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree().size([width, height]);
var svg = d3.select("svg#explainOutput")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var root = treeData[0];
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180 + margin.top; });
// Declare the nodes¦
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
if (!d.id) {
d.id = ++i;
}
return d.id;
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
nodeEnter.append("text")
.attr("x", function(d) {
return -20;}) // d.children || d._children ? -13 : 13; })
.attr("dy", "-20")
.attr("text-anchor", function(d) {
return "start"; }) // d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.type.replace("Node",""); })
.style("fill-opacity", 1);
nodeEnter.append("text")
.attr("x", function(d) {
return -20;}) // d.children || d._children ? -13 : 13; })
.attr("dy", "20")
.attr("text-anchor", function(d) {
return "start"; }) // d.children || d._children ? "end" : "start"; })
.text(function(d) { return "Cost: " + d.estimatedCost; })
.style("fill-opacity", 1);
// Declare the links¦
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
},
showExplainPlan: function(plan) {
$("svg#explainOutput").html();
var nodes = plan.nodes;
if (nodes.length > 0) {
// Handle root element differently
var treeData = this.preparePlanNodeEntry(nodes[0]);
this.followQueryPath(treeData, nodes);
this.drawTree([treeData]);
}
},
showExplainWarnings: function(warnings) {
$(".explain-warnings").html(this.warningTemplate.render({warnings: warnings}));
},
fillExplain: function(callback) {
var self = this;
$("svg#explainOutput").html();
$.ajax({
type: "POST",
url: "/_api/explain",
data: this.readQueryData(),
contentType: "application/json",
processData: false,
success: function (data) {
self.showExplainWarnings(data.warnings);
self.showExplainPlan(data.plan);
if (typeof callback === "function") {
callback();
}
},
error: function (errObj) {
var res = errObj.responseJSON;
// Display ErrorMessage
console.log("Error:", res.errorMessage);
}
});
},
fillResult: function(callback) {
var self = this;
var outputEditor = ace.edit("queryOutput");
// clear result
outputEditor.setValue('');
/*window.progressView.show(
"Query is operating...",
self.abortQuery("id"),
'<button class="button-danger">Abort Query</button>'
);*/
window.progressView.show(
"Query is operating..."
);
this.execPending = false;
$.ajax({
type: "POST",
url: "/_api/cursor",
data: JSON.stringify(data),
data: this.readQueryData(),
contentType: "application/json",
processData: false,
success: function (data) {
@ -633,6 +778,9 @@
window.progressView.hide();
self.deselect(outputEditor);
$('#downloadQueryResult').show();
if (typeof callback === "function") {
callback();
}
},
error: function (data) {
self.switchTab("result-switch");
@ -647,11 +795,27 @@
arangoHelper.arangoError("Query error", "ERROR");
}
window.progressView.hide();
if (typeof callback === "function") {
callback();
}
}
});
outputEditor.resize();
this.deselect(inputEditor);
},
submitQuery: function () {
var outputEditor = ace.edit("queryOutput");
this.fillExplain();
this.fillResult(this.switchTab.bind(this, "result-switch"));
outputEditor.resize();
var inputEditor = ace.edit("aqlEditor");
this.deselect(inputEditor);
},
explainQuery: function() {
this.fillExplain(this.switchTab.bind(this, "explain-switch"));
this.execPending = true;
var inputEditor = ace.edit("aqlEditor");
this.deselect(inputEditor);
},
// This function changes the focus onto the tab that has been clicked
@ -664,8 +828,14 @@
// The convention is #result-switch (a-tag), #result (content-div), and
// #tabContentResult (pane-div).
// We set the clicked element's tags to active/show and the others to hide.
var switchId = typeof e === 'string' ? e : e.target.id;
var changeTab = function (element, index, array){
var switchId;
if (typeof e === 'string') {
switchId = e;
} else {
switchId = e.target.id;
}
var self = this;
var changeTab = function (element){
var divId = "#" + element.replace("-switch", "");
var contentDivId = "#tabContent" + divId.charAt(1).toUpperCase() + divId.substr(2);
if (element === switchId) {
@ -675,6 +845,8 @@
if (switchId === 'query-switch') {
// issue #1000: set focus to query input
$('#aqlEditor .ace_text-input').focus();
} else if (switchId === 'result-switch' && self.execPending) {
self.fillResult();
}
} else {
$("#" + element).parent().removeClass("active");

View File

@ -328,3 +328,23 @@
.queryImport {
margin-bottom: 10px;
}
svg.explain-tree {
overflow: auto;
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
}

View File

@ -5283,6 +5283,19 @@ pre.gv-object-view {
.queryImport {
margin-bottom: 10px; }
svg.explain-tree {
overflow: auto; }
svg.explain-tree .node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px; }
svg.explain-tree .node {
font: 10px sans-serif; }
svg.explain-tree .link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px; }
.display-none {
display: none; }