diff --git a/superset/assets/javascripts/SqlLab/actions.js b/superset/assets/javascripts/SqlLab/actions.js
index d1fbfea46fdb2..04a9a5eae4c4b 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 a66d80ea6b4af..24e9e25db630c 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 5a2cd04232e27..57be300b43e6f 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}
/>
@@ -200,6 +225,12 @@ class SqlEditor extends React.PureComponent {