From 5d7c9a9427509d7cc5cdbcb9f576e2c719f4a19a Mon Sep 17 00:00:00 2001 From: Tai Dupree Date: Fri, 20 Mar 2020 16:39:24 -0700 Subject: [PATCH 1/4] [SQLLAB] add checkbox to control autocomplete --- .../javascripts/sqllab/SqlEditor_spec.jsx | 10 ++ ...EditorWrapper.jsx => AceEditorWrapper.tsx} | 115 ++++++++++-------- .../src/SqlLab/components/SqlEditor.jsx | 16 +++ 3 files changed, 89 insertions(+), 52 deletions(-) rename superset-frontend/src/SqlLab/components/{AceEditorWrapper.jsx => AceEditorWrapper.tsx} (75%) diff --git a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx index cb1002f5de97..a97cebdbab68 100644 --- a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx @@ -18,6 +18,7 @@ */ import React from 'react'; import { shallow } from 'enzyme'; +import { Checkbox } from 'react-bootstrap'; import { defaultQueryEditor, initialState, queries, table } from './fixtures'; import { @@ -105,4 +106,13 @@ describe('SqlEditor', () => { queryEditor.queryLimit, ); }); + it('allows toggling autocomplete', () => { + const wrapper = shallow(); + expect(wrapper.find(AceEditorWrapper).props().autocomplete).toBe(true); + wrapper + .find(Checkbox) + .props() + .onChange(); + expect(wrapper.find(AceEditorWrapper).props().autocomplete).toBe(false); + }); }); diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper.jsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx similarity index 75% rename from superset-frontend/src/SqlLab/components/AceEditorWrapper.jsx rename to superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx index aa31827d6684..682ebcb4be6b 100644 --- a/superset-frontend/src/SqlLab/components/AceEditorWrapper.jsx +++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx @@ -17,7 +17,6 @@ * under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import AceEditor from 'react-ace'; import 'brace/mode/sql'; import 'brace/theme/github'; @@ -34,41 +33,44 @@ import { const langTools = ace.acequire('ace/ext/language_tools'); -const propTypes = { - actions: PropTypes.object.isRequired, - onBlur: PropTypes.func, - sql: PropTypes.string.isRequired, - schemas: PropTypes.array, - tables: PropTypes.array, - functionNames: PropTypes.array, - extendedTables: 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, - }), - ), - onChange: PropTypes.func, +type HotKey = { + key: string; + descr: string; + name: string; + func: () => void; }; -const defaultProps = { - onBlur: () => {}, - onChange: () => {}, - schemas: [], - tables: [], - functionNames: [], - extendedTables: [], -}; +interface Props { + actions: { + queryEditorSetSelectedText: (edit: any, text: null | string) => void; + addTable: (queryEditor: any, value: any, schema: any) => void; + }; + autocomplete: boolean; + onBlur?: (sql: string) => void; + sql: string; + schemas?: any[]; + tables?: any[]; + functionNames?: string[]; + extendedTables?: Array<{ name: string; columns: any[] }>; + queryEditor: any; + height?: string; + hotkeys: HotKey[]; + onChange?: (sql: string) => void; +} + +interface State { + sql: string; + selectedText: string; + words: any[]; +} -class AceEditorWrapper extends React.PureComponent { - constructor(props) { +class AceEditorWrapper extends React.PureComponent { + constructor(props: Props) { super(props); this.state = { sql: props.sql, selectedText: '', + words: [], }; this.onChange = this.onChange.bind(this); } @@ -77,13 +79,16 @@ class AceEditorWrapper extends React.PureComponent { this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null); this.setAutoCompleter(this.props); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: Props) { if ( - !areArraysShallowEqual(this.props.tables, nextProps.tables) || - !areArraysShallowEqual(this.props.schemas, nextProps.schemas) || + !areArraysShallowEqual(this.props.tables || [], nextProps.tables || []) || + !areArraysShallowEqual( + this.props.schemas || [], + nextProps.schemas || [], + ) || !areArraysShallowEqual( - this.props.extendedTables, - nextProps.extendedTables, + this.props.extendedTables || [], + nextProps.extendedTables || [], ) ) { this.setAutoCompleter(nextProps); @@ -93,12 +98,12 @@ class AceEditorWrapper extends React.PureComponent { } } onBlur() { - this.props.onBlur(this.state.sql); + if (this.props.onBlur) this.props.onBlur(this.state.sql); } onAltEnter() { - this.props.onBlur(this.state.sql); + if (this.props.onBlur) this.props.onBlur(this.state.sql); } - onEditorLoad(editor) { + onEditorLoad(editor: any) { editor.commands.addCommand({ name: 'runQuery', bindKey: { win: 'Alt-enter', mac: 'Alt-enter' }, @@ -129,18 +134,24 @@ class AceEditorWrapper extends React.PureComponent { } }); } - onChange(text) { + onChange(text: string) { this.setState({ sql: text }); - this.props.onChange(text); + if (this.props.onChange) this.props.onChange(text); } - getCompletions(aceEditor, session, pos, prefix, callback) { + getCompletions( + aceEditor: any, + session: any, + pos: any, + prefix: string, + callback: (p0: any, p1: any[]) => void, + ) { // If the prefix starts with a number, don't try to autocomplete with a // table name or schema or anything else if (!isNaN(parseInt(prefix, 10))) { return; } const completer = { - insertMatch: (editor, data) => { + insertMatch: (editor: any, data: any) => { if (data.meta === 'table') { this.props.actions.addTable( this.props.queryEditor, @@ -163,7 +174,7 @@ class AceEditorWrapper extends React.PureComponent { }); callback(null, words); } - setAutoCompleter(props) { + setAutoCompleter(props: Props) { // Loading schema, table and column names as auto-completable words const schemas = props.schemas || []; const schemaWords = schemas.map(s => ({ @@ -197,12 +208,14 @@ class AceEditorWrapper extends React.PureComponent { meta: 'column', })); - const functionWords = props.functionNames.map(func => ({ - name: func, - value: func, - score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE, - meta: 'function', - })); + const functionWords = props.functionNames + ? props.functionNames.map(func => ({ + name: func, + value: func, + score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE, + meta: 'function', + })) + : []; const words = schemaWords .concat(tableWords) @@ -223,7 +236,7 @@ class AceEditorWrapper extends React.PureComponent { const validationResult = this.props.queryEditor.validationResult; const resultIsReady = validationResult && validationResult.completed; if (resultIsReady && validationResult.errors.length > 0) { - const errors = validationResult.errors.map(err => ({ + const errors = validationResult.errors.map((err: any) => ({ type: 'error', row: err.line_number - 1, column: err.start_column - 1, @@ -244,14 +257,12 @@ class AceEditorWrapper extends React.PureComponent { onChange={this.onChange} width="100%" editorProps={{ $blockScrolling: true }} - enableLiveAutocompletion + enableLiveAutocompletion={this.props.autocomplete} value={this.state.sql} annotations={this.getAceAnnotations()} /> ); } } -AceEditorWrapper.defaultProps = defaultProps; -AceEditorWrapper.propTypes = propTypes; export default AceEditorWrapper; diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index 138093f9f6c0..3098209096d8 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx @@ -20,6 +20,7 @@ import React from 'react'; import { CSSTransition } from 'react-transition-group'; import PropTypes from 'prop-types'; import { + Checkbox, FormGroup, InputGroup, Form, @@ -93,6 +94,7 @@ class SqlEditor extends React.PureComponent { northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT, southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT, sql: props.queryEditor.sql, + autocomplete: true, }; this.sqlEditorRef = React.createRef(); this.northPaneRef = React.createRef(); @@ -245,6 +247,9 @@ class SqlEditor extends React.PureComponent { handleWindowResize() { this.setState({ height: this.getSqlEditorHeight() }); } + handleToggleAutocomplete = () => { + this.setState({ autocomplete: !this.state.autocomplete }); + }; elementStyle(dimension, elementSize, gutterSize) { return { [dimension]: `calc(${elementSize}% - ${gutterSize + @@ -337,6 +342,7 @@ class SqlEditor extends React.PureComponent {
+ + + {t('Autocomplete')} + + { From f8749871cf6f6fdde9565950ea4df7056e038092 Mon Sep 17 00:00:00 2001 From: Tai Dupree Date: Mon, 23 Mar 2020 11:04:33 -0700 Subject: [PATCH 2/4] autocomplete -> autocompleteEnabled --- superset-frontend/src/SqlLab/components/SqlEditor.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index 3098209096d8..6bc8e241b04e 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx @@ -94,7 +94,7 @@ class SqlEditor extends React.PureComponent { northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT, southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT, sql: props.queryEditor.sql, - autocomplete: true, + autocompleteEnabled: true, }; this.sqlEditorRef = React.createRef(); this.northPaneRef = React.createRef(); @@ -247,8 +247,8 @@ class SqlEditor extends React.PureComponent { handleWindowResize() { this.setState({ height: this.getSqlEditorHeight() }); } - handleToggleAutocomplete = () => { - this.setState({ autocomplete: !this.state.autocomplete }); + handleToggleAutocompleteEnabled = () => { + this.setState({ autocompleteEnabled: !this.state.autocompleteEnabled }); }; elementStyle(dimension, elementSize, gutterSize) { return { @@ -342,7 +342,7 @@ class SqlEditor extends React.PureComponent {
Date: Mon, 23 Mar 2020 11:23:28 -0700 Subject: [PATCH 3/4] fix defaultProps --- .../SqlLab/components/AceEditorWrapper.tsx | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx index 682ebcb4be6b..370cdbcb910c 100644 --- a/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx +++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx @@ -46,16 +46,16 @@ interface Props { addTable: (queryEditor: any, value: any, schema: any) => void; }; autocomplete: boolean; - onBlur?: (sql: string) => void; + onBlur: (sql: string) => void; sql: string; - schemas?: any[]; - tables?: any[]; - functionNames?: string[]; - extendedTables?: Array<{ name: string; columns: any[] }>; + schemas: any[]; + tables: any[]; + functionNames: string[]; + extendedTables: Array<{ name: string; columns: any[] }>; queryEditor: any; - height?: string; + height: string; hotkeys: HotKey[]; - onChange?: (sql: string) => void; + onChange: (sql: string) => void; } interface State { @@ -65,6 +65,15 @@ interface State { } class AceEditorWrapper extends React.PureComponent { + static defaultProps = { + onBlur: () => {}, + onChange: () => {}, + schemas: [], + tables: [], + functionNames: [], + extendedTables: [], + }; + constructor(props: Props) { super(props); this.state = { @@ -81,14 +90,11 @@ class AceEditorWrapper extends React.PureComponent { } UNSAFE_componentWillReceiveProps(nextProps: Props) { if ( - !areArraysShallowEqual(this.props.tables || [], nextProps.tables || []) || + !areArraysShallowEqual(this.props.tables, nextProps.tables) || + !areArraysShallowEqual(this.props.schemas, nextProps.schemas) || !areArraysShallowEqual( - this.props.schemas || [], - nextProps.schemas || [], - ) || - !areArraysShallowEqual( - this.props.extendedTables || [], - nextProps.extendedTables || [], + this.props.extendedTables, + nextProps.extendedTables, ) ) { this.setAutoCompleter(nextProps); @@ -98,10 +104,10 @@ class AceEditorWrapper extends React.PureComponent { } } onBlur() { - if (this.props.onBlur) this.props.onBlur(this.state.sql); + this.props.onBlur(this.state.sql); } onAltEnter() { - if (this.props.onBlur) this.props.onBlur(this.state.sql); + this.props.onBlur(this.state.sql); } onEditorLoad(editor: any) { editor.commands.addCommand({ @@ -136,7 +142,7 @@ class AceEditorWrapper extends React.PureComponent { } onChange(text: string) { this.setState({ sql: text }); - if (this.props.onChange) this.props.onChange(text); + this.props.onChange(text); } getCompletions( aceEditor: any, @@ -208,14 +214,12 @@ class AceEditorWrapper extends React.PureComponent { meta: 'column', })); - const functionWords = props.functionNames - ? props.functionNames.map(func => ({ - name: func, - value: func, - score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE, - meta: 'function', - })) - : []; + const functionWords = props.functionNames.map(func => ({ + name: func, + value: func, + score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE, + meta: 'function', + })); const words = schemaWords .concat(tableWords) From c5cb18fd4604393f35db2f4eb469f75d11dc6267 Mon Sep 17 00:00:00 2001 From: Tai Dupree Date: Mon, 23 Mar 2020 12:26:54 -0700 Subject: [PATCH 4/4] fix spec --- superset-frontend/src/SqlLab/components/SqlEditor.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index 6bc8e241b04e..11edbf7c4d81 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx @@ -510,10 +510,10 @@ class SqlEditor extends React.PureComponent {
{t('Autocomplete')}