Commit 01bbcf4e by David Committed by GitHub

Editor: New line on Enter, run query on Shift+Enter (#24654)

* Editor: New line on Enter, run query on Shift+Enter

- default Enter behavior on query editor fields should be a new line
- special behavior should require a special key: running a query is now
done on Shift-Enter
- Plugins order had to be changed because when typeahead is shown, Enter
is accepting the suggestion

* Run with ctrl-enter, hint in query placeholder

* Fix Kusto field behavior for Enter

* Fix Kusto field behavior for default suggestion
parent e11504dc
...@@ -69,10 +69,12 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS ...@@ -69,10 +69,12 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
// Base plugins // Base plugins
this.plugins = [ this.plugins = [
NewlinePlugin(), // SuggestionsPlugin and RunnerPlugin need to be before NewlinePlugin
// because they override Enter behavior
SuggestionsPlugin({ onTypeahead, cleanText, portalOrigin, onWillApplySuggestion }), SuggestionsPlugin({ onTypeahead, cleanText, portalOrigin, onWillApplySuggestion }),
ClearPlugin(),
RunnerPlugin({ handler: this.runOnChangeAndRunQuery }), RunnerPlugin({ handler: this.runOnChangeAndRunQuery }),
NewlinePlugin(),
ClearPlugin(),
SelectionShortcutsPlugin(), SelectionShortcutsPlugin(),
IndentationPlugin(), IndentationPlugin(),
ClipboardPlugin(), ClipboardPlugin(),
......
...@@ -23,7 +23,7 @@ export function NewlinePlugin(): Plugin { ...@@ -23,7 +23,7 @@ export function NewlinePlugin(): Plugin {
return next(); return next();
} }
if (keyEvent.key === 'Enter' && keyEvent.shiftKey) { if (keyEvent.key === 'Enter') {
keyEvent.preventDefault(); keyEvent.preventDefault();
const { startBlock } = value; const { startBlock } = value;
......
...@@ -8,10 +8,14 @@ describe('runner', () => { ...@@ -8,10 +8,14 @@ describe('runner', () => {
const mockHandler = jest.fn(); const mockHandler = jest.fn();
const handler = RunnerPlugin({ handler: mockHandler }).onKeyDown!; const handler = RunnerPlugin({ handler: mockHandler }).onKeyDown!;
it('should execute query when enter is pressed and there are no suggestions visible', () => { it('should execute query when enter with shift is pressed', () => {
const value = Plain.deserialize(''); const value = Plain.deserialize('');
const editor = shallow<Editor>(<Editor value={value} />); const editor = shallow<Editor>(<Editor value={value} />);
handler({ key: 'Enter', preventDefault: () => {} } as KeyboardEvent, editor.instance() as any, () => {}); handler(
{ key: 'Enter', shiftKey: true, preventDefault: () => {} } as KeyboardEvent,
editor.instance() as any,
() => {}
);
expect(mockHandler).toBeCalled(); expect(mockHandler).toBeCalled();
}); });
}); });
...@@ -7,11 +7,11 @@ export function RunnerPlugin({ handler }: any): Plugin { ...@@ -7,11 +7,11 @@ export function RunnerPlugin({ handler }: any): Plugin {
const keyEvent = event as KeyboardEvent; const keyEvent = event as KeyboardEvent;
// Handle enter // Handle enter
if (handler && keyEvent.key === 'Enter' && !keyEvent.shiftKey) { if (handler && keyEvent.key === 'Enter' && (keyEvent.shiftKey || keyEvent.ctrlKey)) {
// Submit on Enter // Submit on Enter
keyEvent.preventDefault(); keyEvent.preventDefault();
handler(keyEvent); handler(keyEvent);
return true; return editor;
} }
return next(); return next();
......
...@@ -97,7 +97,15 @@ export function SuggestionsPlugin({ ...@@ -97,7 +97,15 @@ export function SuggestionsPlugin({
break; break;
case 'Enter': case 'Enter': {
if (!(keyEvent.shiftKey || keyEvent.ctrlKey) && hasSuggestions) {
keyEvent.preventDefault();
return typeaheadRef.insertSuggestion();
}
break;
}
case 'Tab': { case 'Tab': {
if (hasSuggestions) { if (hasSuggestions) {
keyEvent.preventDefault(); keyEvent.preventDefault();
...@@ -108,7 +116,10 @@ export function SuggestionsPlugin({ ...@@ -108,7 +116,10 @@ export function SuggestionsPlugin({
} }
default: { default: {
// Don't react on meta keys
if (keyEvent.key.length === 1) {
handleTypeaheadDebounced(editor, setState, onTypeahead, cleanText); handleTypeaheadDebounced(editor, setState, onTypeahead, cleanText);
}
break; break;
} }
} }
......
...@@ -357,7 +357,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs ...@@ -357,7 +357,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent<CloudWatchLogs
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
onTypeahead={this.onTypeahead} onTypeahead={this.onTypeahead}
cleanText={cleanText} cleanText={cleanText}
placeholder="Enter a CloudWatch Logs Insights query" placeholder="Enter a CloudWatch Logs Insights query (run with Shift+Enter)"
portalOrigin="cloudwatch" portalOrigin="cloudwatch"
syntaxLoaded={syntaxLoaded} syntaxLoaded={syntaxLoaded}
disabled={loadingLogGroups || selectedLogGroups.length === 0} disabled={loadingLogGroups || selectedLogGroups.length === 0}
......
...@@ -71,7 +71,7 @@ class ElasticsearchQueryField extends React.PureComponent<Props, State> { ...@@ -71,7 +71,7 @@ class ElasticsearchQueryField extends React.PureComponent<Props, State> {
query={query.query} query={query.query}
onChange={this.onChangeQuery} onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
placeholder="Enter a Lucene query" placeholder="Enter a Lucene query (run with Shift+Enter)"
portalOrigin="elasticsearch" portalOrigin="elasticsearch"
syntaxLoaded={syntaxLoaded} syntaxLoaded={syntaxLoaded}
/> />
......
...@@ -73,7 +73,7 @@ class QueryField extends React.Component<any, any> { ...@@ -73,7 +73,7 @@ class QueryField extends React.Component<any, any> {
labelKeys: {}, labelKeys: {},
labelValues: {}, labelValues: {},
suggestions: [], suggestions: [],
typeaheadIndex: 0, typeaheadIndex: null,
typeaheadPrefix: '', typeaheadPrefix: '',
value: getInitialValue(props.initialQuery || ''), value: getInitialValue(props.initialQuery || ''),
}; };
...@@ -144,10 +144,10 @@ class QueryField extends React.Component<any, any> { ...@@ -144,10 +144,10 @@ class QueryField extends React.Component<any, any> {
case 'Tab': case 'Tab':
case 'Enter': { case 'Enter': {
if (this.menuEl) { if (this.menuEl && typeaheadIndex !== null) {
// Dont blur input // Dont blur input
keyboardEvent.preventDefault(); keyboardEvent.preventDefault();
if (!suggestions || !suggestions.length) { if (!suggestions || !suggestions.length || keyboardEvent.shiftKey || keyboardEvent.ctrlKey) {
return next(); return next();
} }
...@@ -166,7 +166,7 @@ class QueryField extends React.Component<any, any> { ...@@ -166,7 +166,7 @@ class QueryField extends React.Component<any, any> {
if (this.menuEl) { if (this.menuEl) {
// Select next suggestion // Select next suggestion
keyboardEvent.preventDefault(); keyboardEvent.preventDefault();
this.setState({ typeaheadIndex: typeaheadIndex + 1 }); this.setState({ typeaheadIndex: (typeaheadIndex || 0) + 1 });
} }
break; break;
} }
...@@ -175,7 +175,7 @@ class QueryField extends React.Component<any, any> { ...@@ -175,7 +175,7 @@ class QueryField extends React.Component<any, any> {
if (this.menuEl) { if (this.menuEl) {
// Select previous suggestion // Select previous suggestion
keyboardEvent.preventDefault(); keyboardEvent.preventDefault();
this.setState({ typeaheadIndex: Math.max(0, typeaheadIndex - 1) }); this.setState({ typeaheadIndex: Math.max(0, (typeaheadIndex || 0) - 1) });
} }
break; break;
} }
...@@ -203,7 +203,7 @@ class QueryField extends React.Component<any, any> { ...@@ -203,7 +203,7 @@ class QueryField extends React.Component<any, any> {
this.setState( this.setState(
{ {
suggestions: [], suggestions: [],
typeaheadIndex: 0, typeaheadIndex: null,
typeaheadPrefix: '', typeaheadPrefix: '',
typeaheadContext: null, typeaheadContext: null,
}, },
...@@ -298,19 +298,20 @@ class QueryField extends React.Component<any, any> { ...@@ -298,19 +298,20 @@ class QueryField extends React.Component<any, any> {
renderMenu = () => { renderMenu = () => {
const { portalPrefix } = this.props; const { portalPrefix } = this.props;
const { suggestions } = this.state; const { suggestions, typeaheadIndex } = this.state;
const hasSuggesstions = suggestions && suggestions.length > 0; const hasSuggesstions = suggestions && suggestions.length > 0;
if (!hasSuggesstions) { if (!hasSuggesstions) {
return null; return null;
} }
// Guard selectedIndex to be within the length of the suggestions // Guard selectedIndex to be within the length of the suggestions
let selectedIndex = Math.max(this.state.typeaheadIndex, 0); let selectedIndex = Math.max(typeaheadIndex, 0);
const flattenedSuggestions = flattenSuggestions(suggestions); const flattenedSuggestions = flattenSuggestions(suggestions);
selectedIndex = selectedIndex % flattenedSuggestions.length || 0; selectedIndex = selectedIndex % flattenedSuggestions.length || 0;
const selectedKeys = (flattenedSuggestions.length > 0 ? [flattenedSuggestions[selectedIndex]] : []).map(i => const selectedKeys = (typeaheadIndex !== null && flattenedSuggestions.length > 0
typeof i === 'object' ? i.text : i ? [flattenedSuggestions[selectedIndex]]
); : []
).map(i => (typeof i === 'object' ? i.text : i));
// Create typeahead in DOM root so we can later position it absolutely // Create typeahead in DOM root so we can later position it absolutely
return ( return (
......
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
<button class="btn btn-primary width-10" ng-click="ctrl.refresh()">Run</button> <button class="btn btn-primary width-10" ng-click="ctrl.refresh()">Run</button>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label">(New Line: Shift+Enter, Run Query: Enter, Trigger Suggestion: Ctrl+Space)</label> <label class="gf-form-label">(Run Query: Shift+Enter, Trigger Suggestion: Ctrl+Space)</label>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
......
...@@ -174,7 +174,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr ...@@ -174,7 +174,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
onChange={this.onChangeQuery} onChange={this.onChangeQuery}
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
placeholder="Enter a Loki query" placeholder="Enter a Loki query (run with Shift+Enter)"
portalOrigin="loki" portalOrigin="loki"
syntaxLoaded={syntaxLoaded} syntaxLoaded={syntaxLoaded}
/> />
......
...@@ -340,7 +340,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -340,7 +340,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
onChange={this.onChangeQuery} onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery} onRunQuery={this.props.onRunQuery}
placeholder="Enter a PromQL query" placeholder="Enter a PromQL query (run with Shift+Enter)"
portalOrigin="prometheus" portalOrigin="prometheus"
syntaxLoaded={syntaxLoaded} syntaxLoaded={syntaxLoaded}
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment