diff --git a/caravel/assets/javascripts/SqlLab/actions.js b/caravel/assets/javascripts/SqlLab/actions.js index 9634a6a83ab5b..6e46ae22851d6 100644 --- a/caravel/assets/javascripts/SqlLab/actions.js +++ b/caravel/assets/javascripts/SqlLab/actions.js @@ -17,6 +17,7 @@ export const QUERY_EDITOR_SET_SCHEMA = 'QUERY_EDITOR_SET_SCHEMA'; export const QUERY_EDITOR_SET_TITLE = 'QUERY_EDITOR_SET_TITLE'; export const QUERY_EDITOR_SET_AUTORUN = 'QUERY_EDITOR_SET_AUTORUN'; export const QUERY_EDITOR_SET_SQL = 'QUERY_EDITOR_SET_SQL'; +export const QUERY_EDITOR_SET_SELECTED_TEXT = 'QUERY_EDITOR_SET_SELECTED_TEXT'; export const SET_DATABASES = 'SET_DATABASES'; export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR'; export const ADD_ALERT = 'ADD_ALERT'; @@ -193,6 +194,10 @@ export function queryEditorSetSql(queryEditor, sql) { return { type: QUERY_EDITOR_SET_SQL, queryEditor, sql }; } +export function queryEditorSetSelectedText(queryEditor, sql) { + return { type: QUERY_EDITOR_SET_SELECTED_TEXT, queryEditor, sql }; +} + export function mergeTable(table) { return { type: MERGE_TABLE, table }; } diff --git a/caravel/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx b/caravel/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx index ae2e5af9b726d..cd7069498c9f7 100644 --- a/caravel/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx +++ b/caravel/assets/javascripts/SqlLab/components/AceEditorWrapper.jsx @@ -4,13 +4,16 @@ import 'brace/mode/sql'; import 'brace/theme/github'; import 'brace/ext/language_tools'; import ace from 'brace'; +import { areArraysShallowEqual } from '../../reduxUtils'; const langTools = ace.acequire('ace/ext/language_tools'); const propTypes = { + actions: React.PropTypes.object.isRequired, onBlur: React.PropTypes.func, sql: React.PropTypes.string.isRequired, tables: React.PropTypes.array, + queryEditor: React.PropTypes.object.isRequired, }; const defaultProps = { @@ -25,6 +28,16 @@ class AceEditorWrapper extends React.PureComponent { sql: props.sql, }; } + componentDidMount() { + // Making sure no text is selected from previous mount + this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null); + this.setAutoCompleter(); + } + componentWillReceiveProps(nextProps) { + if (!areArraysShallowEqual(this.props.tables, nextProps.tables)) { + this.setAutoCompleter(); + } + } textChange(text) { this.setState({ sql: text }); } @@ -32,22 +45,31 @@ class AceEditorWrapper extends React.PureComponent { this.props.onBlur(this.state.sql); } getCompletions(aceEditor, session, pos, prefix, callback) { + callback(null, this.state.words); + } + onEditorLoad(editor) { + editor.$blockScrolling = Infinity; // eslint-disable-line no-param-reassign + editor.selection.on('changeSelection', () => { + this.props.actions.queryEditorSetSelectedText( + this.props.queryEditor, editor.getSelectedText()); + }); + } + setAutoCompleter() { + // Loading table and column names as auto-completable words let words = []; const columns = {}; const tables = this.props.tables || []; tables.forEach(t => { words.push({ name: t.name, value: t.name, score: 55, meta: 'table' }); - t.columns.forEach(col => { + const cols = t.columns || []; + cols.forEach(col => { columns[col.name] = null; // using an object as a unique set }); }); words = words.concat(Object.keys(columns).map(col => ( { name: col, value: col, score: 50, meta: 'column' } ))); - callback(null, words); - } - setAutoCompleter() { - // Loading table and column names as auto-completable words + this.setState({ words }); const completer = { getCompletions: this.getCompletions.bind(this), }; @@ -56,11 +78,11 @@ class AceEditorWrapper extends React.PureComponent { } } render() { - this.setAutoCompleter(); return ( ); diff --git a/caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx b/caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx index ab91024eb59cf..5ae681e58c572 100644 --- a/caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx +++ b/caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx @@ -58,12 +58,13 @@ class SqlEditor extends React.PureComponent { this.startQuery(runAsync); } startQuery(runAsync = false, ctas = false) { + const qe = this.props.queryEditor; const query = { - dbId: this.props.queryEditor.dbId, - sql: this.props.queryEditor.sql, - sqlEditorId: this.props.queryEditor.id, - tab: this.props.queryEditor.title, - schema: this.props.queryEditor.schema, + dbId: qe.dbId, + sql: qe.selectedText ? qe.selectedText : qe.sql, + sqlEditorId: qe.id, + tab: qe.title, + schema: qe.schema, tempTableName: this.state.ctas, runAsync, ctas, @@ -92,17 +93,23 @@ class SqlEditor extends React.PureComponent { render() { let runButtons = []; + let runText = 'Run Query'; + let btnStyle = 'primary'; + if (this.props.queryEditor.selectedText) { + runText = 'Run Selection'; + btnStyle = 'warning'; + } if (this.props.database && this.props.database.allow_run_sync) { runButtons.push( ); } @@ -110,7 +117,7 @@ class SqlEditor extends React.PureComponent { runButtons.push(