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 <mblow@apache.org>
Integration-Tests: Michael Blow <mblow@apache.org>
Tested-by: Michael Blow <mblow@apache.org>
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 <alexandre.bodelot@gmail.com>
+ */
+(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>