Murtadha Hubail has submitted this change and it was merged. Change subject: ASTERIXDB-1083: Fixed non-query statements' plans display on WebUI ......................................................................
ASTERIXDB-1083: Fixed non-query statements' plans display on WebUI Change-Id: I52dd69062b2aaf89798ebb8e0e58a1941ac4119e Reviewed-on: https://asterix-gerrit.ics.uci.edu/390 Reviewed-by: Chris Hillery <[email protected]> Tested-by: Jenkins <[email protected]> --- M asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java M asterix-app/src/main/java/org/apache/asterix/api/http/servlet/APIServlet.java M asterix-app/src/main/java/org/apache/asterix/aql/translator/AqlTranslator.java M asterix-app/src/main/resources/webui/querytemplate.html 4 files changed, 101 insertions(+), 56 deletions(-) Approvals: Chris Hillery: Looks good to me, approved Jenkins: Verified diff --git a/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java b/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java index 0031e0d..91d8358 100644 --- a/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java +++ b/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java @@ -23,8 +23,6 @@ import java.util.ArrayList; import java.util.List; -import org.json.JSONException; - import org.apache.asterix.api.common.Job.SubmissionMode; import org.apache.asterix.aql.base.Statement.Kind; import org.apache.asterix.aql.expression.FunctionDecl; @@ -78,12 +76,14 @@ import org.apache.hyracks.api.client.IHyracksClientConnection; import org.apache.hyracks.api.job.JobId; import org.apache.hyracks.api.job.JobSpecification; +import org.json.JSONException; /** * Provides helper methods for compilation of a query into a JobSpec and submission * to Hyracks through the Hyracks client interface. */ public class APIFramework { + public static final String HTML_STATEMENT_SEPARATOR = "<!-- BEGIN -->"; private static List<Pair<AbstractRuleController, List<IAlgebraicRewriteRule>>> buildDefaultLogicalRewrites() { List<Pair<AbstractRuleController, List<IAlgebraicRewriteRule>>> defaultLogicalRewrites = new ArrayList<Pair<AbstractRuleController, List<IAlgebraicRewriteRule>>>(); diff --git a/asterix-app/src/main/java/org/apache/asterix/api/http/servlet/APIServlet.java b/asterix-app/src/main/java/org/apache/asterix/api/http/servlet/APIServlet.java index 39a5644..35d4c37 100644 --- a/asterix-app/src/main/java/org/apache/asterix/api/http/servlet/APIServlet.java +++ b/asterix-app/src/main/java/org/apache/asterix/api/http/servlet/APIServlet.java @@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.asterix.api.common.APIFramework; import org.apache.asterix.api.common.SessionConfig; import org.apache.asterix.api.common.SessionConfig.OutputFormat; import org.apache.asterix.aql.base.Statement; @@ -63,15 +64,12 @@ String output = request.getParameter("output-format"); if (output.equals("ADM")) { format = OutputFormat.ADM; - } - else if (output.equals("CSV")) { + } else if (output.equals("CSV")) { format = OutputFormat.CSV; - } - else if (output.equals("CSV-Header")) { + } else if (output.equals("CSV-Header")) { format = OutputFormat.CSV; csv_and_header = true; - } - else { + } else { // Default output format format = OutputFormat.JSON; } @@ -106,8 +104,7 @@ sessionConfig.set(SessionConfig.FORMAT_HTML, true); sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, csv_and_header); sessionConfig.setOOBData(isSet(printExprParam), isSet(printRewrittenExprParam), - isSet(printLogicalPlanParam), isSet(printOptimizedLogicalPlanParam), - isSet(printJob)); + isSet(printLogicalPlanParam), isSet(printOptimizedLogicalPlanParam), isSet(printJob)); MetadataManager.INSTANCE.init(); AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, sessionConfig); double duration = 0; @@ -115,6 +112,7 @@ aqlTranslator.compileAndExecute(hcc, hds, AqlTranslator.ResultDelivery.SYNC); long endTime = System.currentTimeMillis(); duration = (endTime - startTime) / 1000.00; + out.println(APIFramework.HTML_STATEMENT_SEPARATOR); out.println("<PRE>Duration of all jobs: " + duration + " sec</PRE>"); } catch (ParseException | TokenMgrError | org.apache.asterix.aqlplus.parser.TokenMgrError pe) { GlobalConfig.ASTERIX_LOGGER.log(Level.INFO, pe.toString(), pe); diff --git a/asterix-app/src/main/java/org/apache/asterix/aql/translator/AqlTranslator.java b/asterix-app/src/main/java/org/apache/asterix/aql/translator/AqlTranslator.java index a684cac..dcfbc98 100644 --- a/asterix-app/src/main/java/org/apache/asterix/aql/translator/AqlTranslator.java +++ b/asterix-app/src/main/java/org/apache/asterix/aql/translator/AqlTranslator.java @@ -259,6 +259,9 @@ Map<String, String> config = new HashMap<String, String>(); for (Statement stmt : aqlStatements) { + if (sessionConfig.is(SessionConfig.FORMAT_HTML)) { + sessionConfig.out().println(APIFramework.HTML_STATEMENT_SEPARATOR); + } validateOperation(activeDefaultDataverse, stmt); AqlMetadataProvider metadataProvider = new AqlMetadataProvider(activeDefaultDataverse, CentralFeedManager.getInstance()); diff --git a/asterix-app/src/main/resources/webui/querytemplate.html b/asterix-app/src/main/resources/webui/querytemplate.html index ee49122..9088251 100644 --- a/asterix-app/src/main/resources/webui/querytemplate.html +++ b/asterix-app/src/main/resources/webui/querytemplate.html @@ -16,13 +16,41 @@ ! specific language governing permissions and limitations ! under the License. !--> +<!DOCTYPE html> +<html lang="en"> +<head> +<meta name="description" content="ASTERIX WEB PAGE" /> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link + href='http://fonts.googleapis.com/css?family=Bitter|PT+Sans+Caption|Open+Sans' + rel='stylesheet' type='text/css'> +<script src="/webui/static/js/jquery.min.js"></script> + +<link href="/webui/static/css/bootstrap.min.css" rel="stylesheet" + type="text/css" /> +<link href="/webui/static/css/bootstrap-responsive.min.css" + rel="stylesheet" type="text/css" /> + +<script src="/webui/static/js/bootstrap.min.js"></script> + +<link href="/webui/static/css/style.css" rel="stylesheet" + type="text/css" /> + +<script type="text/javascript"> +$(document).ready(function() { + + var optionButtonSize = $('#checkboxes-on').width(); + $('#clear-query-button, #run-btn').width(optionButtonSize); + + $('#checkboxes-on').click(function() { + /* Displays a checkmark to indicate selection/clearing */ if ($('#opts').is(":visible")) { $('#opts').hide(); $('#queryform :input').prop('checked', false); } else { - $('#opts').show(); + $('#opts').show(); $('#queryform :input').prop('checked', true); - } + } return false; }); @@ -31,7 +59,7 @@ return false; }); - $('form#queryform :input').click( function () { + $('form#queryform :input').click(function() { /* Hides selection check on uncheck, shows when all 5 selected */ if ($(this).val()) { if ($(this).is(':checked') && $('input[type=checkbox]').filter(':checked').length == 5) { @@ -45,74 +73,90 @@ $("form#queryform").submit(function() { $('#output-message').html(""); $.post("/", $("form#queryform").serialize(), function(data) { - - var resSet = 0; - var resPattern = /<h4>Results:<\/h4>/g; - var durPattern = /<PRE>Duration/g; + var durPattern = '<PRE>Duration'; var errorPattern = /<div class="accordion" id="errorblock">/g; - var resultCount = data.match(resPattern); - - if (!resPattern.test(data)) { - if(errorPattern.test(data)) { - $('#output-heading').html('Error'); - $('#output-heading').addClass('error'); - } else { - $('#output-heading').html('Output'); - $('#output-heading').removeClass('error'); - } - $('#output-message').html(data); + var sectionsSeparator = '<h4>'; + var resultPat = 'Results:</h4>'; + + if (errorPattern.test(data)) { + $('#output-heading').html('Error'); + $('#output-heading').addClass('error'); } else { $('#output-heading').html('Output'); $('#output-heading').removeClass('error'); - if (resultCount.length <= 1) { - $('#output-message').html(data); - } else { - var splitData = data.split('<PRE>Duration'); - var results = splitData[0].split('<h4>'); - var components = results.slice(1, results.length); - var sections = components.length / resultCount.length; + } + var executedStatements = data.split('<!-- BEGIN -->'); + var executedStatementsWithResultsCount = 0; + for (var i = 0; i < executedStatements.length; i++) { + if (executedStatements[i].toString().trim().length > 0) { + /* check how many statements have returned data*/ + executedStatementsWithResultsCount++; + } + } - for (resSet = 0; resSet < resultCount.length; resSet++) { + /* only a single statement returned results and/or duration message*/ + if (executedStatementsWithResultsCount <= 2) { + /* print statement results and duration*/ + $('#output-message').html(data); + } else { + var resultsCount = 1; + /* need to create collapse button and div per statement*/ + for (var i = 0; i < executedStatements.length; i++) { + /* last statement is always the duration message*/ + if (i == (executedStatements.length - 1)) { + /* print duration message*/ + $('#output-message').append(executedStatements[i]); + break; + } - $('#output-message').append('<h4>' + components[(resSet+1)*sections - 1]); + if (executedStatements[i].toString().trim().length > 0) { + var sections = executedStatements[i].toString().split(sectionsSeparator); + /* remove the first section since it is always empty due to splitng on sectionsSeparator */ + sections.splice(0, 1); - if (sections > 1) { - var resNum = resSet + 1; + /* if there is a results section, we need to put it before the collapsible section*/ + for (var j = 0; j < sections.length; j++) { + /* print results section and remove it*/ + if (sections[j].indexOf(resultPat) >= 0) { + var resultsSection = sections.splice(j, 1); + $('#output-message').append(sectionsSeparator + resultsSection.toString()); + } + } + + if (sections.length > 0) { + /* generate the collapsible section for this statement*/ $('<button/>') .attr("class", "btn") .attr("data-toggle", "collapse") - .attr("data-target", "#collapse" + resSet) + .attr("data-target", "#collapse" + i) .css("margin-bottom", "1em") - .html('Result Plan #' + resNum + '<i id="ibtn' + resSet + '" class="icon-plus extarget"></i>') + .html('Result Plan #' + resultsCount + '<i id="ibtn' + resultsCount + '" class="icon-plus extarget"></i>') .appendTo('#output-message'); $('<div/>') - .attr("id", "collapse" + resSet) + .attr("id", "collapse" + i) .attr("class", "collapse in") .appendTo('#output-message'); - for (var c = 0; c < sections - 1; c++) { - var pos = resSet*sections + c; - $('#collapse' + resSet).append('<h4>' + components[pos]); + /* put the rest of the sections in the collapsible section*/ + for (var k = 0; k < sections.length; k++) { + $('#collapse' + i).append(sectionsSeparator + sections[k].toString()); } - - /* Placeholder for future on show/hide result plan behavior - $('#collapse' + resSet).on('show', function() { - }).on('hide', function() { - }); - */ - - $('#output-message').append("<hr/>"); } + $('#output-message').append("<hr/>"); + resultsCount++; + /* Placeholder for future on show/hide result plan behavior + $('#collapse' + resSet).on('show', function() { + }).on('hide', function() { + }); + */ } - $('#output-message').append('<PRE>Duration' + splitData[1]); } - } var contentString = data.toString(); - if (contentString.indexOf("<PRE>Duration") !== -1) { + if (contentString.indexOf(durPattern) != -1) { $('<div/>') .addClass("alert alert-success") .html("Success: Query Complete") -- To view, visit https://asterix-gerrit.ics.uci.edu/390 To unsubscribe, visit https://asterix-gerrit.ics.uci.edu/settings Gerrit-MessageType: merged Gerrit-Change-Id: I52dd69062b2aaf89798ebb8e0e58a1941ac4119e Gerrit-PatchSet: 3 Gerrit-Project: asterixdb Gerrit-Branch: master Gerrit-Owner: Murtadha Hubail <[email protected]> Gerrit-Reviewer: Chris Hillery <[email protected]> Gerrit-Reviewer: Ian Maxon <[email protected]> Gerrit-Reviewer: Jenkins <[email protected]> Gerrit-Reviewer: Murtadha Hubail <[email protected]>
