Commit 2bc8f31b authored by Jérome Perrin's avatar Jérome Perrin

sql_browser: use monaco editor

Use monaco editor with https://github.com/joe-re/sql-language-server
completion provider bundled in
https://lab.nexedi.com/jerome/monaco-editor-sql-completion-provider
This completion provider is not perfect, but codemirror's one was not so
good either. At least we can use monaco editor.

Also add a quick "copy to cliboard" feature, which copies the table as
html or in markdown format for text.

And also fix Server-Timing header that was an obsolete syntax no longer
supported by chrome.

/reviewed-on nexedi/erp5!1036
parent e9130ca7
Pipeline #7703 passed with stage
in 0 seconds
...@@ -32,6 +32,6 @@ for line in data[1:]: ...@@ -32,6 +32,6 @@ for line in data[1:]:
new_line.append(v) new_line.append(v)
new_data.append(new_line) new_data.append(new_line)
response.setHeader("Server-Timing", "db=%s;" % (time.time() - start)) response.setHeader("Server-Timing", 'db;dur=%s;desc="SQL query"' % (time.time() - start))
response.setHeader('Content-Type', 'application/json') response.setHeader('Content-Type', 'application/json')
return json.dumps(new_data, indent=2) return json.dumps(new_data, indent=2)
from json import dumps from json import dumps
table_dict = {}
connection = context connection = context
# make sure connection is open # make sure connection is open
connection() connection()
tables = []
for table in connection.manage_test("SHOW TABLES"): for table in connection.manage_test("SHOW TABLES"):
table_dict[table[0]] = [] columns = []
for column in connection.manage_test("SHOW COLUMNS FROM `%s`" % table[0]): for column in connection.manage_test("SHOW FULL COLUMNS FROM `%s`" % table[0]):
table_dict[table[0]].append(column[0]) columns.append({
table_dict[table[0]].append("`%s`" % column[0]) 'columnName': column.field,
'description': column.comment,
})
tables.append(
{
'tableName': table[0],
'columns': columns
}
)
container.REQUEST.RESPONSE.setHeader('content-type', 'application/json') container.REQUEST.RESPONSE.setHeader('content-type', 'application/json')
return dumps(table_dict) return dumps(tables)
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
<!-- "local" jquery noty --> <!-- "local" jquery noty -->
<script type="text/javascript" src="sql_browser/jquery.noty.packaged.js"></script> <script type="text/javascript" src="sql_browser/jquery.noty.packaged.js"></script>
<!-- build from https://lab.nexedi.com/jerome/monaco-editor-sql-completion-provider/ -->
<script type="text/javascript" src="sql_browser/monaco-editor-sql-completion-provider.bundle.js"></script>
<!-- "local" c3.js (0.4.10) and d3.js (3.5.5) --> <!-- "local" c3.js (0.4.10) and d3.js (3.5.5) -->
<link rel="stylesheet" type="text/css" href="sql_browser/c3.min.css"> <link rel="stylesheet" type="text/css" href="sql_browser/c3.min.css">
<script type="text/javascript" src="sql_browser/d3.min.js"></script> <script type="text/javascript" src="sql_browser/d3.min.js"></script>
...@@ -25,25 +29,17 @@ ...@@ -25,25 +29,17 @@
<script type="text/javascript" src="pivottable/gchart_renderers.js"></script> <script type="text/javascript" src="pivottable/gchart_renderers.js"></script>
<script type="text/javascript" src="pivottable/c3_renderers.js"></script> <script type="text/javascript" src="pivottable/c3_renderers.js"></script>
<!-- monaco editor from erp5_monaco_editor bt -->
<!-- code mirror from erp5_code_mirror bt5 --> <script>
<script type="text/javascript" src="codemirror/lib/codemirror.js"></script> /*
<link rel="stylesheet" href="codemirror/lib/codemirror.css"> This is understood by our build of monaco-editor
<script type="text/javascript" src="codemirror/mode/sql/sql.js"></script> https://lab.nexedi.com/jerome/monaco-editor-erp5/blob/master/public-path.js
<script type="text/javascript" src="codemirror/addon/cm_edit/matchbrackets.js"></script> */
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css"> window.monacoEditorWebPackResourceBaseUrl = "monaco-editor/";
<script type="text/javascript" src="codemirror/addon/dialog/dialog.js"></script> </script>
<script type="text/javascript" src="codemirror/addon/search/searchcursor.js"></script> <script src="monaco-editor/app.bundle.min.js"></script>
<script type="text/javascript" src="codemirror/addon/search/search.js"></script>
<link rel="stylesheet" href="codemirror/addon/hint/show-hint.css">
<script src="codemirror/addon/hint/show-hint.js"></script>
<script src="codemirror/addon/hint/anyword-hint.js"></script>
<script src="codemirror/addon/hint/sql-hint.js"></script>
<style> <style>
.CodeMirror {height: 80px;}
.CodeMirror-hints {z-index: 100;} /* above handsontable header */
body {font-family: Verdana;} body {font-family: Verdana;}
.c3-line {stroke-width: 3px;} .c3-line {stroke-width: 3px;}
.c3 circle {stroke: white;} .c3 circle {stroke: white;}
...@@ -110,30 +106,68 @@ ...@@ -110,30 +106,68 @@
}; };
$(function() { $(function() {
editor = CodeMirror.fromTextArea(document.getElementById("query"), {
lineNumbers: true, editor = monaco.editor.create(
matchBrackets: true, document.querySelector('#query'),
viewportMargin: Infinity, {
extraKeys: {"Ctrl-Space": "autocomplete", "Ctrl-Enter": redraw, "Alt-Space": redraw}, automaticLayout: true,
mode: "text/x-mariadb" autoIndent: true
}
);
monaco.editor.setModelLanguage(
editor.getModel(),
"sql"
);
editor.addAction({
id: 'execute-query',
label: 'Execute Query',
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
],
contextMenuGroupId: "sql",
run: function() {
redraw()
return null;
}
}); });
$(editor.getWrapperElement()).resizable();
$("#query").resizable();
$.getJSON("ZMySQLDAConnection_getSchemaAsJSON").then( $.getJSON("ZMySQLDAConnection_getSchemaAsJSON").then(
function(schema) { function(schema) {
CodeMirror.commands.autocomplete = function(cm) { monaco.languages.registerCompletionItemProvider('sql', new SQLCompletionProvider(schema));
CodeMirror.showHint(cm, CodeMirror.hint.sql, {
tables: schema
} );
}
}); });
$('button[name="Query"]').click(redraw); $('button[name="Query"]').click(redraw);
$('button[name="Copy"]').click(e => {
const tableAsMarkdown = (([
`|${ht.getColHeader().map(h=> ` ${h} |`).join('') }`,
`|${ht.getColHeader().map(_=> ` ------ |`).join('') }`] ).concat(
ht.getData().map(line => `|${line.map(d => ` ${d === null ? '' : d} |`).join('') }`)
)).join('\n');
const tableAsHTML = (([
`<table><thead><tr>${ht.getColHeader().map(h=> `<th>${h}</th>`).join('') }</tr></thead><tbody><tr>`,
]).concat([
ht.getData().map(line => `${line.map(d => `<td>${d === null ? '' : d}</td>`).join('') }`).join('</tr><tr>')]
).concat([`</tr></tbody></table>`])
).join('\n');
const copyFunc = (event) => {
event.preventDefault();
event.clipboardData.setData('text/plain', tableAsMarkdown);
event.clipboardData.setData('text/html', tableAsHTML);
};
document.addEventListener('copy', copyFunc);
try {
document.execCommand('copy');
} finally {
document.removeEventListener('copy', copyFunc)
}
});
// TODO: save presets in JIO // TODO: save presets in JIO
// ( also include pivot table config in preset ) // ( also include pivot table config in preset )
$('button[name="Save"]').click(function(){alert("TODO");}); $('button[name="Save"]').click(function(){alert("TODO");});
// TODO: now that we use monaco, this could be snippets
$('#presets') $('#presets')
.append($('<option>', { .append($('<option>', {
value: "select * from message_queue where processing_node = -2", value: "select * from message_queue where processing_node = -2",
...@@ -153,12 +187,14 @@ ...@@ -153,12 +187,14 @@
<button name="Save">Save Preset</button> <button name="Save">Save Preset</button>
<br/> <br/>
--> -->
<textarea name="query" id="query"></textarea>
<div style="width: 100%; height: 300px" id="query"> </div>
<button name="Query">Run Query</button> <button name="Query">Run Query</button>
<button name="Copy">Copy Results</button>
<div id="table_container" style="margin: 10px; height: 300px; overflow: hidden; z-index: 1;"></div> <div id="table_container" style="margin: 10px; height: 300px; overflow: hidden; z-index: 1;"></div>
<div id="output" style="margin: 10px;"></div> <div id="output" style="margin: 10px; overflow: scroll"></div>
<!-- TODO: debug saved configuration --> <!-- TODO: debug saved configuration -->
<div style="display: none"> <div style="display: none">
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Cacheable__manager_id</string> </key>
<value> <string>http_cache</string> </value>
</item>
<item>
<key> <string>__name__</string> </key>
<value> <string>monaco-editor-sql-completion-provider.bundle.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>monaco-editor-sql-completion-provider.bundle.js</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_jquery erp5_jquery
erp5_pivot_table erp5_pivot_table
erp5_code_mirror erp5_monaco_editor
\ No newline at end of file \ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment