superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: Hotkeys in SQL Lab (#4680)
Date Tue, 27 Mar 2018 22:54:56 GMT
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new f510956  Hotkeys in SQL Lab (#4680)
f510956 is described below

commit f510956da210e9edc24597b1cced27460eed07ac
Author: Maxime Beauchemin <maximebeauchemin@gmail.com>
AuthorDate: Tue Mar 27 15:54:54 2018 -0700

    Hotkeys in SQL Lab (#4680)
    
    * Hotkeys
    
    * Making it work in AceEditor
    
    * Addressing comments
---
 superset/assets/javascripts/SqlLab/actions.js      |  7 ++-
 .../SqlLab/components/AceEditorWrapper.jsx         | 15 ++++-
 .../javascripts/SqlLab/components/SqlEditor.jsx    | 64 ++++++++++++++++------
 superset/assets/javascripts/components/Hotkeys.jsx | 53 ++++++++++++++++++
 4 files changed, 118 insertions(+), 21 deletions(-)

diff --git a/superset/assets/javascripts/SqlLab/actions.js b/superset/assets/javascripts/SqlLab/actions.js
index d1fbfea..04a9a5e 100644
--- a/superset/assets/javascripts/SqlLab/actions.js
+++ b/superset/assets/javascripts/SqlLab/actions.js
@@ -196,8 +196,11 @@ export function setDatabases(databases) {
 }
 
 export function addQueryEditor(queryEditor) {
-  const newQe = Object.assign({}, queryEditor, { id: shortid.generate() });
-  return { type: ADD_QUERY_EDITOR, queryEditor: newQe };
+  const newQueryEditor = {
+    ...queryEditor,
+    id: shortid.generate(),
+  };
+  return { type: ADD_QUERY_EDITOR, queryEditor: newQueryEditor };
 }
 
 export function cloneQueryToNewTab(query) {
diff --git a/superset/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx b/superset/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx
index a66d80e..24e9e25 100644
--- a/superset/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx
+++ b/superset/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx
@@ -28,16 +28,19 @@ const sqlWords = sqlKeywords.map(s => ({
 const propTypes = {
   actions: PropTypes.object.isRequired,
   onBlur: PropTypes.func,
-  onAltEnter: PropTypes.func,
   sql: PropTypes.string.isRequired,
   tables: PropTypes.array,
   queryEditor: PropTypes.object.isRequired,
   height: PropTypes.string,
+  hotkeys: PropTypes.arrayOf(PropTypes.shape({
+    key: PropTypes.string.isRequired,
+    descr: PropTypes.string.isRequired,
+    func: PropTypes.func.isRequired,
+  })),
 };
 
 const defaultProps = {
   onBlur: () => {},
-  onAltEnter: () => {},
   tables: [],
 };
 
@@ -67,7 +70,6 @@ class AceEditorWrapper extends React.PureComponent {
   }
   onAltEnter() {
     this.props.onBlur(this.state.sql);
-    this.props.onAltEnter();
   }
   onEditorLoad(editor) {
     editor.commands.addCommand({
@@ -77,6 +79,13 @@ class AceEditorWrapper extends React.PureComponent {
         this.onAltEnter();
       },
     });
+    this.props.hotkeys.forEach((keyConfig) => {
+      editor.commands.addCommand({
+        name: keyConfig.name,
+        bindKey: { win: keyConfig.key, mac: keyConfig.key },
+        exec: keyConfig.func,
+      });
+    });
     editor.$blockScrolling = Infinity; // eslint-disable-line no-param-reassign
     editor.selection.on('changeSelection', () => {
       const selectedText = editor.getSelectedText();
diff --git a/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx b/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx
index 5a2cd04..57be300 100644
--- a/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx
+++ b/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx
@@ -21,6 +21,7 @@ import SouthPane from './SouthPane';
 import SaveQuery from './SaveQuery';
 import ShareQuery from './ShareQuery';
 import Timer from '../../components/Timer';
+import Hotkeys from '../../components/Hotkeys';
 import SqlEditorLeftBar from './SqlEditorLeftBar';
 import AceEditorWrapper from './AceEditorWrapper';
 import { STATE_BSSTYLE_MAP } from '../constants';
@@ -46,7 +47,6 @@ const defaultProps = {
   hideLeftBar: false,
 };
 
-
 class SqlEditor extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -57,6 +57,8 @@ class SqlEditor extends React.PureComponent {
 
     this.onResize = this.onResize.bind(this);
     this.throttledResize = throttle(this.onResize, 250);
+    this.runQuery = this.runQuery.bind(this);
+    this.stopQuery = this.stopQuery.bind(this);
   }
   componentWillMount() {
     if (this.state.autorun) {
@@ -86,18 +88,39 @@ class SqlEditor extends React.PureComponent {
       this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight);
     }
   }
+  getHotkeyConfig() {
+    return [
+      {
+        name: 'runQuery',
+        key: 'ctrl+r',
+        descr: 'Run query',
+        func: this.runQuery,
+      },
+      {
+        name: 'newTab',
+        key: 'ctrl+t',
+        descr: 'New tab',
+        func: () => {
+          this.props.actions.addQueryEditor({
+            ...this.props.queryEditor,
+            title: t('Untitled Query'),
+            sql: '',
+          });
+        },
+      },
+      {
+        name: 'stopQuery',
+        key: 'ctrl+x',
+        descr: 'Stop query',
+        func: this.stopQuery,
+      },
+    ];
+  }
   setQueryEditorSql(sql) {
     this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
   }
-  runQuery(runAsync = false) {
-    if (!this.props.queryEditor.sql) {
-      return;
-    }
-    let effectiveRunAsync = runAsync;
-    if (!this.props.database.allow_run_sync) {
-      effectiveRunAsync = true;
-    }
-    this.startQuery(effectiveRunAsync);
+  runQuery() {
+    this.startQuery(!this.props.database.allow_run_sync);
   }
   startQuery(runAsync = false, ctas = false) {
     const qe = this.props.queryEditor;
@@ -116,7 +139,9 @@ class SqlEditor extends React.PureComponent {
     this.props.actions.setActiveSouthPaneTab('Results');
   }
   stopQuery() {
-    this.props.actions.postStopQuery(this.props.latestQuery);
+    if (this.props.latestQuery && this.props.latestQuery.state === 'running') {
+      this.props.actions.postStopQuery(this.props.latestQuery);
+    }
   }
   createTableAs() {
     this.startQuery(true, true);
@@ -128,7 +153,7 @@ class SqlEditor extends React.PureComponent {
     const horizontalScrollbarHeight = 25;
     return parseInt(this.props.getHeight(), 10) - horizontalScrollbarHeight;
   }
-  renderEditorBottomBar() {
+  renderEditorBottomBar(hotkeys) {
     let ctasControls;
     if (this.props.database && this.props.database.allow_ctas) {
       const ctasToolTip = t('Create table as with query results');
@@ -181,9 +206,9 @@ class SqlEditor extends React.PureComponent {
                 allowAsync={this.props.database ? this.props.database.allow_run_async : false}
                 dbId={qe.dbId}
                 queryState={this.props.latestQuery && this.props.latestQuery.state}
-                runQuery={this.runQuery.bind(this)}
+                runQuery={this.runQuery}
                 selectedText={qe.selectedText}
-                stopQuery={this.stopQuery.bind(this)}
+                stopQuery={this.stopQuery}
               />
             </span>
             <span className="m-r-5">
@@ -200,6 +225,12 @@ class SqlEditor extends React.PureComponent {
               <ShareQuery queryEditor={qe} />
             </span>
             {ctasControls}
+            <span className="m-l-5">
+              <Hotkeys
+                header="Hotkeys"
+                hotkeys={hotkeys}
+              />
+            </span>
           </Form>
         </div>
         <div className="pull-right">
@@ -226,6 +257,7 @@ class SqlEditor extends React.PureComponent {
   render() {
     const height = this.sqlEditorHeight();
     const defaultNorthHeight = this.props.queryEditor.height || 200;
+    const hotkeys = this.getHotkeyConfig();
     return (
       <div
         className="SqlEditor"
@@ -271,12 +303,12 @@ class SqlEditor extends React.PureComponent {
                     actions={this.props.actions}
                     onBlur={this.setQueryEditorSql.bind(this)}
                     queryEditor={this.props.queryEditor}
-                    onAltEnter={this.runQuery.bind(this)}
                     sql={this.props.queryEditor.sql}
                     tables={this.props.tables}
                     height={((this.state.editorPaneHeight || defaultNorthHeight) - 50) +
'px'}
+                    hotkeys={hotkeys}
                   />
-                  {this.renderEditorBottomBar()}
+                  {this.renderEditorBottomBar(hotkeys)}
                 </div>
               </div>
               <div ref="south">
diff --git a/superset/assets/javascripts/components/Hotkeys.jsx b/superset/assets/javascripts/components/Hotkeys.jsx
new file mode 100644
index 0000000..f6ea114
--- /dev/null
+++ b/superset/assets/javascripts/components/Hotkeys.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { OverlayTrigger, Popover } from 'react-bootstrap';
+import { Table } from 'reactable';
+
+import Mousetrap from 'mousetrap';
+
+const propTypes = {
+  hotkeys: PropTypes.arrayOf(PropTypes.shape({
+    key: PropTypes.string.isRequired,
+    descr: PropTypes.string.isRequired,
+    func: PropTypes.func.isRequired,
+  })).isRequired,
+  header: PropTypes.string,
+};
+
+const defaultProps = {
+  hotkeys: [],
+};
+
+export default class Hotkeys extends React.PureComponent {
+  componentDidMount() {
+    this.props.hotkeys.forEach((keyConfig) => {
+      Mousetrap.bind([keyConfig.key], keyConfig.func);
+    });
+  }
+  renderPopover() {
+    return (
+      <Popover id="popover-hotkeys" title={this.props.header} style={{ width: '300px'
}}>
+        <Table
+          className="table table-condensed"
+          data={this.props.hotkeys.map(keyConfig => ({
+            Key: keyConfig.key,
+            Action: keyConfig.descr,
+          }))}
+        />
+      </Popover>);
+  }
+  render() {
+    return (
+      <OverlayTrigger
+        overlay={this.renderPopover()}
+        trigger={['hover', 'focus']}
+        placement="top"
+      >
+        <i className="fa fa-keyboard-o fa-lg" />
+      </OverlayTrigger>
+    );
+  }
+}
+
+Hotkeys.propTypes = propTypes;
+Hotkeys.defaultProps = defaultProps;

-- 
To stop receiving notification emails like this one, please contact
maximebeauchemin@apache.org.

Mime
View raw message