diff --git a/backend/static/js/helper.js b/backend/static/js/helper.js
index 68cbbb5..c8ffa49 100644
--- a/backend/static/js/helper.js
+++ b/backend/static/js/helper.js
@@ -106,8 +106,10 @@ function appendRuntimeInformation(runtime_info, query, time, queryUpdate) {
}
// Add query time to meta info.
- runtime_info["meta"]["total_time_computing"] =
- parseInt(time["computeResult"].toString().replace(/ms/, ""), 10);
+ if ("computeResult" in time) {
+ runtime_info["meta"]["total_time_computing"] =
+ parseInt(time["computeResult"].toString().replace(/ms/, ""), 10);
+ }
runtime_info["meta"]["total_time"] =
parseInt(time["total"].toString().replace(/ms/, ""), 10);
@@ -755,6 +757,12 @@ function expandEditor() {
}
}
+function displayInErrorBlock(message) {
+ $('#errorReason').html(message);
+ $('#errorBlock').show();
+ $('#answerBlock, #infoBlock, #updatedBlock').hide();
+}
+
function displayError(response, queryId = undefined) {
console.error("Either the GET request failed or the backend returned an error:", response);
if (response["exception"] == undefined || response["exception"] == "") {
@@ -782,9 +790,7 @@ function displayError(response, queryId = undefined) {
queryToDisplay = htmlEscape(queryToDisplay);
}
disp += "Your query was: " + "
" + queryToDisplay + ""; - $('#errorReason').html(disp); - $('#errorBlock').show(); - $('#answerBlock, #infoBlock').hide(); + displayInErrorBlock(disp); // If error response contains query and runtime info, append to runtime log. // @@ -811,7 +817,7 @@ function displayWarning(result) { } function displayStatus(str) { - $("#errorBlock,#answerBlock,#warningBlock").hide(); + $("#errorBlock,#answerBlock,#warningBlock,#updatedBlock").hide(); $("#info").html(str); $("#infoBlock").show(); } diff --git a/backend/static/js/qleverUI.js b/backend/static/js/qleverUI.js index 3895136..fd75d32 100755 --- a/backend/static/js/qleverUI.js +++ b/backend/static/js/qleverUI.js @@ -365,6 +365,21 @@ $(document).ready(function () { document.execCommand("copy"); $(this).parent().parent().parent().find(".ok-text").collapse("show"); }); + + const accessToken = $("#access_token"); + + function updateBackendCommandVisibility() { + if (accessToken.val().trim() === "") { + $("#backend_commands").hide(); + } else { + $("#backend_commands").show(); + } + } + + updateBackendCommandVisibility(); + accessToken.on("input", function () { + updateBackendCommandVisibility(); + }); }); @@ -493,221 +508,337 @@ function createWebSocketForQuery(queryId, startTimeStamp, query) { return ws; } +// Determines the type of the operation (Query or Update) and also subtype +// (Select, Insert Data, etc.) as an object `{type: ..., subtype: ...}`. +function determineOperationType(operation) { + // Strip all PREFIX, BASE and WITH statements from the beginning of the query + const strippedOp = operation + .trim() + .replace(/BASE\s+<[^<]*>\s*/gi, "") + .replace(/PREFIX\s+\w+:\s+<[^<]*>\s*/gi, "") + .replace(/WITH\s+((<[^<]*>)|(\w*:\w*))\s*/gi, ""); + const words = strippedOp.split(/\s+/).map(word => word.toUpperCase()) + // Determine the query type based on the first keywords + switch (words[0]) { + case "SELECT": + return {type: "Query", subtype: "Select"}; + case "CONSTRUCT": + return {type: "Query", subtype: "Construct"}; + case "DESCRIBE": + return {type: "Query", subtype: "Describe"}; + case "ASK": + return {type: "Query", subtype: "Ask"}; + case "LOAD": + return {type: "Update", subtype: "Load"}; + case "CLEAR": + return {type: "Update", subtype: "Clear"}; + case "DROP": + return {type: "Update", subtype: "Drop"}; + case "CREATE": + return {type: "Update", subtype: "Create"}; + case "ADD": + return {type: "Update", subtype: "Add"}; + case "MOVE": + return {type: "Update", subtype: "Move"}; + case "COPY": + return {type: "Update", subtype: "Copy"}; + case "INSERT": + if (words[1] === "DATA") { + return {type: "Update", subtype: "Insert Data"}; + } + return {type: "Update", subtype: "Modify"}; + case "DELETE": + if (words[1] === "DATA") { + return {type: "Update", subtype: "Delete Data"}; + } + else if (words[1] === "WHERE") { + return {type: "Update", subtype: "Delete Where"}; + } + return {type: "Update", subtype: "Modify"}; + default: + return {type: "Unknown", subtype: "Unknown"}; + } +} + +function setRunningIndicator(element) { + const icon = $(element).find('.glyphicon'); + icon.addClass('glyphicon-spin glyphicon-refresh'); + icon.removeClass('glyphicon-remove'); + icon.css('color', $(element).css('color')); +} + +function removeRunningIndicator(element) { + const icon = $(element).find('.glyphicon'); + icon.removeClass('glyphicon-spin'); + if (icon.css("color") !== 'rgb(255, 0, 0)') { + icon.css('color', ''); + } +} + +function setErrorIndicator(element) { + const icon = $(element).find('.glyphicon'); + icon.removeClass('glyphicon-refresh'); + icon.addClass('glyphicon-remove'); + icon.css('color', 'red'); +} + +// Executes a backend command (e.g. `clear-cache`). +async function executeBackendCommand(command, element) { + log("Executing command: " + command); + let headers = {}; + const access_token = $.trim($("#access_token").val()); + if (access_token.length > 0) + headers["Authorization"] = `Bearer ${access_token}`; + const params = {"cmd": command}; + setRunningIndicator(element); + try { + await fetchQleverBackend(params, headers); + } catch (error) { + setErrorIndicator(element) + } finally { + removeRunningIndicator(element); + } +} + // 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')); + setRunningIndicator(element); 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; + let params = {}; + let headers = {}; + let operationType; + console.assert(sendLimit >= 0); + var original_query = editor.getValue(); + var query = await rewriteQuery(original_query, {"name_service": "if_checked"}); + operationType = determineOperationType(query); + console.log(`Determined operation type: ${JSON.stringify(operationType)}`); + switch (operationType.type) { + case "Query": + params["query"] = query; + break; + case "Update": + params["update"] = query; + const access_token = $.trim($("#access_token").val()); + if (access_token.length > 0) headers["Authorization"] = `Bearer ${access_token}`; + break + default: + console.log("Unknown operation type", operationType); + disp = "
Time for query planning: " + time_query_planning +
"
Time for index scans during query planning: " + time_index_scans_query_planning +
diff --git a/backend/templates/index.html b/backend/templates/index.html
index 5dd5c07..327bfea 100644
--- a/backend/templates/index.html
+++ b/backend/templates/index.html
@@ -91,6 +91,21 @@