Skip to content

Commit

Permalink
Address remaining PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinTF committed Oct 13, 2023
1 parent a25e21f commit c09695a
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 73 deletions.
13 changes: 7 additions & 6 deletions backend/static/js/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ function appendRuntimeInformation(runtime_info, query, time, queryUpdate) {
lastQueryUpdate = queryUpdate;
}

// Add "text" field to given `tree_node`, for display using Treant.js (in
// function `visualise` in `qleverUI.js`). This function call itself recursively
// on each child of `tree_node` (if any).
// Add "text" field to given `tree_node`, for display using Treant.js
// (in function `renderRuntimeInformationToDom` in `qleverUI.js`).
// This function call itself recursively on each child of `tree_node` (if any).
//
// NOTE: The labels and the style can be found in backend/static/css/style.css .
// The coloring of the boxes, which depends on the time and caching status, is
// done in function `visualise` in `qleverUI.js`.
// done in function `renderRuntimeInformationToDom` in `qleverUI.js`.
function addTextElementsToQueryExecutionTreeForTreant(tree_node, is_ancestor_cached = false) {
if (tree_node["text"] == undefined) {
var text = {};
Expand Down Expand Up @@ -753,7 +753,7 @@ function expandEditor() {
}
}

function displayError(queryId, response, statusWithText = undefined) {
function displayError(response, statusWithText = undefined, queryId = undefined) {
console.error("Either the GET request failed or the backend returned an error:", response);
if (response["exception"] == undefined || response["exception"] == "") {
response["exception"] = "Unknown error";
Expand Down Expand Up @@ -792,12 +792,13 @@ function displayError(queryId, response, statusWithText = undefined) {
// If error response contains query and runtime info, append to runtime log.
//
// TODO: Show items from error responses in different color (how about "red").
if (response["query"] && response["runtimeInformation"]) {
if (response["query"] && response["runtimeInformation"] && queryId) {
// console.log("DEBUG: Error response with runtime information found!");
appendRuntimeInformation(response.runtimeInformation,
response.query,
response.time,
{ queryId, updateTimeStamp: Number.MAX_VALUE });
renderRuntimeInformationToDom();
}
}

Expand Down
179 changes: 113 additions & 66 deletions backend/static/js/qleverUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var runtime_log = [];
var query_log = [];
var lastQueryUpdate = { queryId: null, updateTimeStamp: 0 };

// Generates a random query id only known to this client
function generateQueryId() {
if (window.isSecureContext) {
return crypto.randomUUID();
Expand All @@ -20,6 +21,11 @@ function generateQueryId() {
return Math.floor(Math.random() * 1000000000);
}

// Uses the BASEURL variable to build the URL for the websocket endpoint
function getWebSocketUrl(queryId) {
return `${BASEURL.replaceAll(/^http/g, "ws")}/watch/${queryId}`;
}

$(window).resize(function (e) {
if (e.target == window) {
editor.setSize($('#queryBlock').width());
Expand Down Expand Up @@ -374,44 +380,16 @@ function getResultTime(resultTimes) {
return timeList;
}

// Process the given query.
async function processQuery(sendLimit=0, element=$("#exebtn")) {
log('Preparing query...', 'other');
log('Element: ' + element, 'other');
if (sendLimit >= 0) { displayStatus("Waiting for response..."); }

$(element).find('.glyphicon').addClass('glyphicon-spin glyphicon-refresh');
$(element).find('.glyphicon').removeClass('glyphicon-remove');
$(element).find('.glyphicon').css('color', $(element).css('color'));
log('Sending request...', 'other');

// A negative value for `sendLimit` has the special meaning: clear the cache
// (without issuing a query). This is used in `backend/templates/index.html`,
// in the definition of the `oncklick` action for the "Clear cache" button.
// TODO: super ugly, find a better solution.
let nothingToShow = false;
var params = {};
if (sendLimit >= 0) {
var original_query = editor.getValue();
var query = await rewriteQuery(original_query, { "name_service": "if_checked" });
params["query"] = query;
if (sendLimit > 0) {
params["send"] = sendLimit;
}
} else {
params["cmd"] = "clear-cache";
nothingToShow = true;
}
const queryId = generateQueryId();
const ws = new WebSocket(`${BASEURL.replaceAll(/^http/g, "ws")}/watch/${queryId}`);
const startTimeStamp = Date.now();

// Makes the UI display a placeholder text
// indicating that the query started but
// we haven't heard back from it yet.
function signalQueryStart(queryId, startTimeStamp, query) {
appendRuntimeInformation(
{
query_execution_tree: null,
meta: {}
},
params["query"],
query,
{
computeResult: "0ms",
total: `${Date.now() - startTimeStamp}ms`
Expand All @@ -421,9 +399,14 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
updateTimeStamp: startTimeStamp
}
);
ws.onopen = () => {
log("Waiting for live updates", "other");
};
renderRuntimeInformationToDom();
}

function createWebSocketForQuery(queryId, startTimeStamp, query) {
const ws = new WebSocket(getWebSocketUrl(queryId));

ws.onopen = () => log("Waiting for live updates", "other");

ws.onmessage = (message) => {
if (typeof message.data !== "string") {
log("Unexpected message format", "other");
Expand All @@ -434,7 +417,7 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
query_execution_tree: payload,
meta: {}
},
params["query"],
query,
{
computeResult: `${payload["total_time"] || (Date.now() - startTimeStamp)}ms`,
total: `${Date.now() - startTimeStamp}ms`
Expand All @@ -444,21 +427,63 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
updateTimeStamp: Date.now()
}
);
visualise(false);
renderRuntimeInformationToDom();
}
};
ws.onerror = () => {
log("Live updates not supported", "other");
};

ws.onerror = () => log("Live updates not supported", "other");

return ws;
}

// Process the given query.
async function processQuery(sendLimit=0, element=$("#exebtn")) {
log('Preparing query...', 'other');
log('Element: ' + element, 'other');
if (sendLimit >= 0) { displayStatus("Waiting for response..."); }

$(element).find('.glyphicon').addClass('glyphicon-spin glyphicon-refresh');
$(element).find('.glyphicon').removeClass('glyphicon-remove');
$(element).find('.glyphicon').css('color', $(element).css('color'));
log('Sending request...', 'other');

// A negative value for `sendLimit` has the special meaning: clear the cache
// (without issuing a query). This is used in `backend/templates/index.html`,
// in the definition of the `oncklick` action for the "Clear cache" button.
// TODO: super ugly, find a better solution.
let nothingToShow = false;
var params = {};
if (sendLimit >= 0) {
var original_query = editor.getValue();
var query = await rewriteQuery(original_query, { "name_service": "if_checked" });
params["query"] = query;
if (sendLimit > 0) {
params["send"] = sendLimit;
}
} else {
params["cmd"] = "clear-cache";
nothingToShow = true;
}

const headers = {
"Content-type": "application/x-www-form-urlencoded",
"Accept": "application/qlever-results+json"
}
let ws = null;
let queryId = undefined;
if (!nothingToShow) {
queryId = generateQueryId();
const startTimeStamp = Date.now();
signalQueryStart(queryId, startTimeStamp, params["query"]);
ws = createWebSocketForQuery(queryId, startTimeStamp, params["query"]);
headers["Query-Id"] = queryId;
}

$.ajax({
method: "POST",
url: BASEURL,
data: $.param(params),
headers: {
"Content-type": "application/x-www-form-urlencoded",
"Accept": "application/qlever-results+json",
"Query-Id": queryId,
},
headers: headers,
success: function (result) {
log('Evaluating and displaying results...', 'other');

Expand All @@ -471,7 +496,10 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
return;
}

if (result.status == "ERROR") { displayError(queryId, result); return; }
if (result.status == "ERROR") {
displayError(result, undefined, queryId);
return;
}
if (result["warnings"].length > 0) { displayWarning(result); }

// Show some statistics (on top of the table).
Expand Down Expand Up @@ -613,7 +641,10 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {

// MAX_VALUE ensures this always has priority over the websocket updates
appendRuntimeInformation(result.runtimeInformation, result.query, result.time, { queryId, updateTimeStamp: Number.MAX_VALUE });
visualise(false);
renderRuntimeInformationToDom();

// Make sure we have no socket that stays open forever
ws.close();
}
}).fail(function (jqXHR, textStatus, errorThrown) {
$(element).find('.glyphicon').removeClass('glyphicon-spin glyphicon-refresh');
Expand All @@ -632,7 +663,12 @@ async function processQuery(sendLimit=0, element=$("#exebtn")) {
}
var statusWithText = jqXHR.status && jqXHR.statusText
? (jqXHR.status + " (" + jqXHR.statusText + ")") : undefined;
displayError(queryId, jqXHR.responseJSON, statusWithText);
displayError(jqXHR.responseJSON, statusWithText, nothingToShow ? undefined : queryId);

// Make sure we have no socket that stays open forever
if (ws) {
ws.close();
}
});
}

Expand Down Expand Up @@ -668,18 +704,22 @@ function handleStatsDisplay() {
$('#statsButton span').html('<i class="glyphicon glyphicon-remove" style="color: red;"></i> Unable to connect to backend');
});
}

function visualise(show, number) {
if (show) {
$("#visualisation").modal("show");
}

// Shows the modal containing the current runtime information tree
function showQueryPlanningTree() {
$("#visualisation").modal("show");
}

// Uses the information inside of runtime_log and query_log
// to populate the DOM with the current runtime information.
// Use showQueryPlanningTree() to display it to the user.
function renderRuntimeInformationToDom(number) {
if (runtime_log.length === 0) {
return;
}

// Get the right entries from the runtime log.
runtime_log_index = number ? number - 1 : runtime_log.length - 1;
let runtime_log_index = number || runtime_log.length - 1;
let runtime_info = runtime_log[runtime_log_index];
let resultQuery = query_log[runtime_log_index];

Expand Down Expand Up @@ -708,11 +748,7 @@ function visualise(show, number) {
);

// Show the query.
resultQuery = resultQuery
.replace(/&/g, "&amp;").replace(/"/g, "&quot;")
.replace(/</g, "&lt;").replace(/>/g, "&gt;")
.replace(/'/g, "&#039;");
$("#result-query").html("<pre>" + resultQuery + "</pre>");
$("#result-query").html($("<pre />", { text: resultQuery }));

// Show the query execution tree (using Treant.js).
addTextElementsToQueryExecutionTreeForTreant(runtime_info["query_execution_tree"]);
Expand All @@ -724,7 +760,7 @@ function visualise(show, number) {
},
nodeStructure: runtime_info["query_execution_tree"]
}
var treant_chart = new Treant(treant_tree);
new Treant(treant_tree);

// For each node, on mouseover show the details.
$("div.node").hover(function () {
Expand Down Expand Up @@ -753,11 +789,22 @@ function visualise(show, number) {
$("p.node-status").filter(function() { return $(this).text() === "optimized out"}).addClass("optimized-out");

if ($('#logRequests').is(':checked')) {
select = "";
for (var i = runtime_log.length; i > runtime_log.length - 10 && i > 0; i--) {
select = '<li class="page-item"><a class="page-link" href="javascript:void(0)" onclick="visualise(true, ' + i + ')">[' + i + ']</a></li>' + select;
const queryHistoryList = $("<ul/>", { class: "pagination" });
for (let i = Math.max(runtime_log.length - 10, 0); i < runtime_log.length; i++) {
const link = $("<a/>", {
class: "page-link",
href: "#",
text: `[${i}]`
});
link.on("click", () => {
renderRuntimeInformationToDom(i);
showQueryPlanningTree();
});
queryHistoryList.append($("<li/>", {
class: "page-item",
append: link
}));
}
select = '<ul class="pagination">' + select + '</ul>';
$('#lastQueries').html(select);
$('#lastQueries').html(queryHistoryList);
}
}
2 changes: 1 addition & 1 deletion backend/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h4 style="margin-top: 0px;">QLever UI Shortcuts:</h4>
<button class="btn btn-default" onclick="processQuery(-1, this);">
<i class="glyphicon glyphicon-refresh"></i> Clear cache
</button>
<button class="btn btn-default" onclick="visualise(true);">
<button class="btn btn-default" onclick="showQueryPlanningTree();">
<i class="glyphicon glyphicon-object-align-vertical"></i> Analysis
</button>
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
Expand Down

0 comments on commit c09695a

Please sign in to comment.