feat: [SQLLAB] add checkbox to control autocomplete (#9338)

* [SQLLAB] add checkbox to control autocomplete

* autocomplete -> autocompleteEnabled

* fix defaultProps

* fix spec
This commit is contained in:
ʈᵃᵢ 2020-03-23 22:11:05 -07:00 committed by GitHub
parent 5d9857544a
commit 866f6f9330
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 39 deletions

View File

@ -18,6 +18,7 @@
*/ */
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Checkbox } from 'react-bootstrap';
import { defaultQueryEditor, initialState, queries, table } from './fixtures'; import { defaultQueryEditor, initialState, queries, table } from './fixtures';
import { import {
@ -105,4 +106,13 @@ describe('SqlEditor', () => {
queryEditor.queryLimit, queryEditor.queryLimit,
); );
}); });
it('allows toggling autocomplete', () => {
const wrapper = shallow(<SqlEditor {...mockedProps} />);
expect(wrapper.find(AceEditorWrapper).props().autocomplete).toBe(true);
wrapper
.find(Checkbox)
.props()
.onChange();
expect(wrapper.find(AceEditorWrapper).props().autocomplete).toBe(false);
});
}); });

View File

@ -17,7 +17,6 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import 'brace/mode/sql'; import 'brace/mode/sql';
import 'brace/theme/github'; import 'brace/theme/github';
@ -34,41 +33,53 @@ import {
const langTools = ace.acequire('ace/ext/language_tools'); const langTools = ace.acequire('ace/ext/language_tools');
const propTypes = { type HotKey = {
actions: PropTypes.object.isRequired, key: string;
onBlur: PropTypes.func, descr: string;
sql: PropTypes.string.isRequired, name: string;
schemas: PropTypes.array, func: () => void;
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,
}; };
const defaultProps = { interface Props {
onBlur: () => {}, actions: {
onChange: () => {}, queryEditorSetSelectedText: (edit: any, text: null | string) => void;
schemas: [], addTable: (queryEditor: any, value: any, schema: any) => void;
tables: [], };
functionNames: [], autocomplete: boolean;
extendedTables: [], 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;
}
class AceEditorWrapper extends React.PureComponent { interface State {
constructor(props) { sql: string;
selectedText: string;
words: any[];
}
class AceEditorWrapper extends React.PureComponent<Props, State> {
static defaultProps = {
onBlur: () => {},
onChange: () => {},
schemas: [],
tables: [],
functionNames: [],
extendedTables: [],
};
constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
sql: props.sql, sql: props.sql,
selectedText: '', selectedText: '',
words: [],
}; };
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
} }
@ -77,7 +88,7 @@ class AceEditorWrapper extends React.PureComponent {
this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null); this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null);
this.setAutoCompleter(this.props); this.setAutoCompleter(this.props);
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: Props) {
if ( 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) ||
@ -98,7 +109,7 @@ class AceEditorWrapper extends React.PureComponent {
onAltEnter() { onAltEnter() {
this.props.onBlur(this.state.sql); this.props.onBlur(this.state.sql);
} }
onEditorLoad(editor) { onEditorLoad(editor: any) {
editor.commands.addCommand({ editor.commands.addCommand({
name: 'runQuery', name: 'runQuery',
bindKey: { win: 'Alt-enter', mac: 'Alt-enter' }, bindKey: { win: 'Alt-enter', mac: 'Alt-enter' },
@ -129,18 +140,24 @@ class AceEditorWrapper extends React.PureComponent {
} }
}); });
} }
onChange(text) { onChange(text: string) {
this.setState({ sql: text }); this.setState({ sql: text });
this.props.onChange(text); 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 // If the prefix starts with a number, don't try to autocomplete with a
// table name or schema or anything else // table name or schema or anything else
if (!isNaN(parseInt(prefix, 10))) { if (!isNaN(parseInt(prefix, 10))) {
return; return;
} }
const completer = { const completer = {
insertMatch: (editor, data) => { insertMatch: (editor: any, data: any) => {
if (data.meta === 'table') { if (data.meta === 'table') {
this.props.actions.addTable( this.props.actions.addTable(
this.props.queryEditor, this.props.queryEditor,
@ -163,7 +180,7 @@ class AceEditorWrapper extends React.PureComponent {
}); });
callback(null, words); callback(null, words);
} }
setAutoCompleter(props) { setAutoCompleter(props: Props) {
// Loading schema, table and column names as auto-completable words // Loading schema, table and column names as auto-completable words
const schemas = props.schemas || []; const schemas = props.schemas || [];
const schemaWords = schemas.map(s => ({ const schemaWords = schemas.map(s => ({
@ -223,7 +240,7 @@ class AceEditorWrapper extends React.PureComponent {
const validationResult = this.props.queryEditor.validationResult; const validationResult = this.props.queryEditor.validationResult;
const resultIsReady = validationResult && validationResult.completed; const resultIsReady = validationResult && validationResult.completed;
if (resultIsReady && validationResult.errors.length > 0) { if (resultIsReady && validationResult.errors.length > 0) {
const errors = validationResult.errors.map(err => ({ const errors = validationResult.errors.map((err: any) => ({
type: 'error', type: 'error',
row: err.line_number - 1, row: err.line_number - 1,
column: err.start_column - 1, column: err.start_column - 1,
@ -244,14 +261,12 @@ class AceEditorWrapper extends React.PureComponent {
onChange={this.onChange} onChange={this.onChange}
width="100%" width="100%"
editorProps={{ $blockScrolling: true }} editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion enableLiveAutocompletion={this.props.autocomplete}
value={this.state.sql} value={this.state.sql}
annotations={this.getAceAnnotations()} annotations={this.getAceAnnotations()}
/> />
); );
} }
} }
AceEditorWrapper.defaultProps = defaultProps;
AceEditorWrapper.propTypes = propTypes;
export default AceEditorWrapper; export default AceEditorWrapper;

View File

@ -20,6 +20,7 @@ import React from 'react';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
Checkbox,
FormGroup, FormGroup,
InputGroup, InputGroup,
Form, Form,
@ -93,6 +94,7 @@ class SqlEditor extends React.PureComponent {
northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT, northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT,
southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT, southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
sql: props.queryEditor.sql, sql: props.queryEditor.sql,
autocompleteEnabled: true,
}; };
this.sqlEditorRef = React.createRef(); this.sqlEditorRef = React.createRef();
this.northPaneRef = React.createRef(); this.northPaneRef = React.createRef();
@ -245,6 +247,9 @@ class SqlEditor extends React.PureComponent {
handleWindowResize() { handleWindowResize() {
this.setState({ height: this.getSqlEditorHeight() }); this.setState({ height: this.getSqlEditorHeight() });
} }
handleToggleAutocompleteEnabled = () => {
this.setState({ autocompleteEnabled: !this.state.autocompleteEnabled });
};
elementStyle(dimension, elementSize, gutterSize) { elementStyle(dimension, elementSize, gutterSize) {
return { return {
[dimension]: `calc(${elementSize}% - ${gutterSize + [dimension]: `calc(${elementSize}% - ${gutterSize +
@ -337,6 +342,7 @@ class SqlEditor extends React.PureComponent {
<div ref={this.northPaneRef} className="north-pane"> <div ref={this.northPaneRef} className="north-pane">
<AceEditorWrapper <AceEditorWrapper
actions={this.props.actions} actions={this.props.actions}
autocomplete={this.state.autocompleteEnabled}
onBlur={this.setQueryEditorSql} onBlur={this.setQueryEditorSql}
onChange={this.onSqlChanged} onChange={this.onSqlChanged}
queryEditor={this.props.queryEditor} queryEditor={this.props.queryEditor}
@ -502,6 +508,16 @@ class SqlEditor extends React.PureComponent {
</Form> </Form>
</div> </div>
<div className="rightItems"> <div className="rightItems">
<span>
<Checkbox
checked={this.state.autocompleteEnabled}
inline
title={t('Autocomplete')}
onChange={this.handleToggleAutocompleteEnabled}
>
{t('Autocomplete')}
</Checkbox>
</span>
<TemplateParamsEditor <TemplateParamsEditor
language="json" language="json"
onChange={params => { onChange={params => {