Michael Blow has submitted this change and it was merged. Change subject: Format JSON output ......................................................................
Format JSON output 1. Added Format JSON output query option. 2. Tweaked front end JS to rerender the JSON result. (Take the HTML element and rerender it with json-viewer plugin). 3. Added jquery.json-viewer library (MIT License) to front end, with several customization: a) Several compatibility bug fix (semi-colon, comment); b) expand logo change. Change-Id: Ieec8489c0a055b01e754bba5f9827a7c1f175567 Reviewed-on: https://asterix-gerrit.ics.uci.edu/1574 Reviewed-by: Michael Blow <[email protected]> Integration-Tests: Michael Blow <[email protected]> Tested-by: Michael Blow <[email protected]> --- M asterixdb/LICENSE M asterixdb/asterix-app/src/main/appended-resources/META-INF/LICENSE M asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/ResultPrinter.java M asterixdb/asterix-app/src/main/resources/webui/querytemplate.html A asterixdb/asterix-app/src/main/resources/webui/static/css/jquery.json-viewer.css A asterixdb/asterix-app/src/main/resources/webui/static/js/jquery.json-viewer.js M asterixdb/src/main/licenses/templates/source_licenses.ftl 7 files changed, 288 insertions(+), 2 deletions(-) Approvals: Michael Blow: Looks good to me, approved; Verified; Verified diff --git a/asterixdb/LICENSE b/asterixdb/LICENSE index 2d57e6e..6c7306b 100644 --- a/asterixdb/LICENSE +++ b/asterixdb/LICENSE @@ -456,6 +456,34 @@ Source files in asterix-hivecompat are derived from portions of Apache Hive Query Language v0.13.0 (org.apache.hive:hive-exec). --- + Portions of the AsterixDB WebUI + located at: + asterix-app/src/main/resources/webui/static/js/jquery.json-viewer.js, + and + asterix-app/src/main/resources/webui/static/css/jquery.json-viewer.css + + are available under The MIT License: +--- + Copyright (c) 2014 Alexandre Bodelot + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +--- Portions of the AsterixDB API examples located at: asterix-examples/src/main/resources/admaql101-demo/bottle.py, diff --git a/asterixdb/asterix-app/src/main/appended-resources/META-INF/LICENSE b/asterixdb/asterix-app/src/main/appended-resources/META-INF/LICENSE index 78371a6..9da59a1 100644 --- a/asterixdb/asterix-app/src/main/appended-resources/META-INF/LICENSE +++ b/asterixdb/asterix-app/src/main/appended-resources/META-INF/LICENSE @@ -246,3 +246,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- + Portions of the AsterixDB WebUI + located at: + webui/static/js/jquery.json-viewer.js + and + webui/static/css/jquery.json-viewer.css + are available under The MIT License: +--- + The MIT License (MIT) + + Copyright (c) 2014 Alexandre Bodelot + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +--- \ No newline at end of file diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/ResultPrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/ResultPrinter.java index f401576..088d153 100644 --- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/ResultPrinter.java +++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/ResultPrinter.java @@ -87,7 +87,7 @@ // output by displayCSVHeader(), so skip it here if (conf.is(SessionConfig.FORMAT_HTML)) { conf.out().println("<h4>Results:</h4>"); - conf.out().println("<pre>"); + conf.out().println("<pre class=\"result-content\">"); } try { diff --git a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html index 878b1cf..84d1410 100644 --- a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html +++ b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html @@ -35,8 +35,10 @@ <link href="/webui/static/css/style.css" rel="stylesheet" type="text/css" /> +<script src="/webui/static/js/jquery.json-viewer.js"></script> +<link href="/webui/static/css/jquery.json-viewer.css" type="text/css" rel="stylesheet" /> -<script type="text/javascript"> + <script type="text/javascript"> $(document).ready(function() { var optionButtonSize = $('#checkboxes-on').width(); @@ -155,6 +157,28 @@ } } + /* Handling Pretty JSON */ + var resultFormat = $('#output-format option:checked').val(); + var prettyJson = $('[name="pretty-json"]').is(':checked'); + if ( prettyJson && (resultFormat == 'LOSSLESS_JSON' || resultFormat == 'CLEAN_JSON')) { + $('.result-content').each( + function(idx) { + var results = $(this).text().split('\n'); + $(this).css('padding-left', '20px'); + $(this).text(''); + for (var iter1 = 0; iter1 < results.length - 1; iter1++) { + if (results[iter1].length < 1) { + continue; + } + var resultJSON = $.parseJSON(results[iter1]); + $(this).append($('<div/>').attr("id", "json-record"+idx+"-"+iter1)); + $('#json-record'+idx+"-"+iter1).jsonViewer(resultJSON); + } + } + ); + } + + var contentString = data.toString(); if (contentString.indexOf(durPattern) != -1) { $('<div/>') @@ -235,6 +259,7 @@ <option value="LOSSLESS_JSON">JSON (lossless)</option> </select> </label> + <label class="optlabel"><input type="checkbox" name="pretty-json" value="true" /> Format JSON</label> <label class="optlabel"><input type="checkbox" name="wrapper-array" value="true" /> Wrap results in outer array</label> <label class="checkbox optlabel"><input type="checkbox" name="print-expr-tree" value="true" /> Print parsed expressions</label> <label class="checkbox optlabel"><input type="checkbox" name="print-rewritten-expr-tree" value="true" /> Print rewritten expressions</label> diff --git a/asterixdb/asterix-app/src/main/resources/webui/static/css/jquery.json-viewer.css b/asterixdb/asterix-app/src/main/resources/webui/static/css/jquery.json-viewer.css new file mode 100644 index 0000000..d6143f9 --- /dev/null +++ b/asterixdb/asterix-app/src/main/resources/webui/static/css/jquery.json-viewer.css @@ -0,0 +1,45 @@ +/* Syntax highlighting for JSON objects */ +ul.json-dict, ol.json-array { + list-style-type: none; + margin: 0 0 0 1px; + border-left: 1px dotted #ccc; + padding-left: 2em; +} +.json-string { + color: #0B7500; +} +.json-literal { + color: #1A01CC; + font-weight: bold; +} + +/* Toggle button */ +a.json-toggle { + position: relative; + color: inherit; + text-decoration: none; +} +a.json-toggle:focus { + outline: none; +} +a.json-toggle:before { + color: #aaa; + content: "\25BC"; /* down arrow */ + position: absolute; + display: inline-block; + width: 1em; + left: -1em; +} +a.json-toggle.collapsed:before { + content: "\25B6"; /* left arrow */ +} + +/* Collapsable placeholder links */ +a.json-placeholder { + color: #aaa; + padding: 0 1em; + text-decoration: none; +} +a.json-placeholder:hover { + text-decoration: underline; +} diff --git a/asterixdb/asterix-app/src/main/resources/webui/static/js/jquery.json-viewer.js b/asterixdb/asterix-app/src/main/resources/webui/static/js/jquery.json-viewer.js new file mode 100644 index 0000000..6ef9bdd --- /dev/null +++ b/asterixdb/asterix-app/src/main/resources/webui/static/js/jquery.json-viewer.js @@ -0,0 +1,137 @@ +/** + * jQuery json-viewer + * @author: Alexandre Bodelot <[email protected]> + */ +(function($){ + + /** + * Check if arg is either an array with at least 1 element, or a dict with at least 1 key + * @return boolean + */ + function isCollapsable(arg) { + return arg instanceof Object && Object.keys(arg).length > 0; + } + + /** + * Check if a string represents a valid url + * @return boolean + */ + function isUrl(string) { + var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + return regexp.test(string); + } + + /** + * Transform a json object into html representation + * @return string + */ + function json2html(json, options) { + var html = ''; + if (typeof json === 'string') { + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + if (isUrl(json)) + html += '<a href="' + json + '" class="json-string">' + json + '</a>'; + else + html += '<span class="json-string">"' + json + '"</span>'; + } + else if (typeof json === 'number') { + html += '<span class="json-literal">' + json + '</span>'; + } + else if (typeof json === 'boolean') { + html += '<span class="json-literal">' + json + '</span>'; + } + else if (json === null) { + html += '<span class="json-literal">null</span>'; + } + else if (json instanceof Array) { + if (json.length > 0) { + html += '[<ol class="json-array">'; + for (var i = 0; i < json.length; ++i) { + html += '<li>'; + if (isCollapsable(json[i])) { + html += '<a href class="json-toggle"></a>'; + } + html += json2html(json[i], options); + if (i < json.length - 1) { + html += ','; + } + html += '</li>'; + } + html += '</ol>]'; + } + else { + html += '[]'; + } + } + else if (typeof json === 'object') { + var key_count = Object.keys(json).length; + if (key_count > 0) { + html += '{<ul class="json-dict">'; + for (var key in json) { + if (json.hasOwnProperty(key)) { + html += '<li>'; + var keyRepr = options.withQuotes ? + '<span class="json-string">"' + key + '"</span>' : key; + if (isCollapsable(json[key])) { + html += '<a href class="json-toggle">' + keyRepr + '</a>'; + } + else { + html += keyRepr; + } + html += ': ' + json2html(json[key], options); + if (--key_count > 0) + html += ','; + html += '</li>'; + } + } + html += '</ul>}'; + } + else { + html += '{}'; + } + } + return html; + } + + /** + * jQuery plugin method + * @param json: a javascript object + * @param options: an optional options hash + */ + $.fn.jsonViewer = function(json, options) { + options = options || {}; + + return this.each(function() { + + var html = json2html(json, options); + if (isCollapsable(json)) + html = '<a href class="json-toggle"></a>' + html; + + $(this).html(html); + + $(this).off('click'); + $(this).on('click', 'a.json-toggle', function() { + var target = $(this).toggleClass('collapsed').siblings('ul.json-dict, ol.json-array'); + target.toggle(); + if (target.is(':visible')) { + target.siblings('.json-placeholder').remove(); + } + else { + var count = target.children('li').length; + var placeholder = count + (count > 1 ? ' items' : ' item'); + target.after('<a href class="json-placeholder">' + placeholder + '</a>'); + } + return false; + }); + + $(this).on('click', 'a.json-placeholder', function() { + $(this).siblings('a.json-toggle').click(); + return false; + }); + + if (options.collapsed == true) { + $(this).find('a.json-toggle').click(); + } + }); + }; +})(jQuery); diff --git a/asterixdb/src/main/licenses/templates/source_licenses.ftl b/asterixdb/src/main/licenses/templates/source_licenses.ftl index 191d953..a42de40 100644 --- a/asterixdb/src/main/licenses/templates/source_licenses.ftl +++ b/asterixdb/src/main/licenses/templates/source_licenses.ftl @@ -227,3 +227,25 @@ location="${hivecompatLocation!}" filePrefix="${hivecompatPrefix!}"> Source files in asterix-hivecompat are derived from portions of Apache Hive Query Language v0.13.0 (org.apache.hive:hive-exec). </@license> +<@license component="AsterixDB WebUI" licenseName="The MIT License" + files=["webui/static/js/jquery.json-viewer.js","webui/static/css/jquery.json-viewer.css"]> + Copyright (c) 2014 Alexandre Bodelot + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +</@license> -- To view, visit https://asterix-gerrit.ics.uci.edu/1574 To unsubscribe, visit https://asterix-gerrit.ics.uci.edu/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ieec8489c0a055b01e754bba5f9827a7c1f175567 Gerrit-PatchSet: 7 Gerrit-Project: asterixdb Gerrit-Branch: master Gerrit-Owner: Xikui Wang <[email protected]> Gerrit-Reviewer: Jenkins <[email protected]> Gerrit-Reviewer: Michael Blow <[email protected]> Gerrit-Reviewer: Till Westmann <[email protected]> Gerrit-Reviewer: Xikui Wang <[email protected]>
