mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[KQL] Better warning messages when using Lucene-like syntax (#31837)
* Update grammar to show lucene errors * Add warning message * Add tests * Fix test & add opt out message * Fix message namespace * Fix tests
This commit is contained in:
parent
6449e59255
commit
a5cb224d14
7 changed files with 1695 additions and 203 deletions
|
@ -418,4 +418,81 @@ describe('kuery AST API', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('doesKueryExpressionHaveLuceneSyntaxError', function () {
|
||||||
|
it('should return true for Lucene ranges', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: [1 TO 10]');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for KQL ranges', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar < 1');
|
||||||
|
expect(result).to.eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene exists', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('_exists_: bar');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for KQL exists', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar:*');
|
||||||
|
expect(result).to.eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene wildcards', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba?');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for KQL wildcards', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba*');
|
||||||
|
expect(result).to.eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene regex', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: /ba.*/');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene fuzziness', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba~');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene proximity', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: "ba"~2');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene boosting', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba^2');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene + operator', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('+foo: bar');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene - operators', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('-foo: bar');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene && operators', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar && baz: qux');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for Lucene || operators', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar || baz: qux');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for mixed KQL/Lucene queries', function () {
|
||||||
|
const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar and (baz: qux || bag)');
|
||||||
|
expect(result).to.eql(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
2
packages/kbn-es-query/src/kuery/ast/ast.d.ts
vendored
2
packages/kbn-es-query/src/kuery/ast/ast.d.ts
vendored
|
@ -46,3 +46,5 @@ export function fromKueryExpression(
|
||||||
): KueryNode;
|
): KueryNode;
|
||||||
|
|
||||||
export function toElasticsearchQuery(node: KueryNode, indexPattern: StaticIndexPattern): JsonObject;
|
export function toElasticsearchQuery(node: KueryNode, indexPattern: StaticIndexPattern): JsonObject;
|
||||||
|
|
||||||
|
export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean;
|
||||||
|
|
|
@ -61,3 +61,12 @@ export function toElasticsearchQuery(node, indexPattern) {
|
||||||
|
|
||||||
return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern);
|
return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doesKueryExpressionHaveLuceneSyntaxError(expression) {
|
||||||
|
try {
|
||||||
|
fromExpression(expression, { errorOnLuceneSyntax: true }, parseKuery);
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
return (e.message.startsWith('Lucene'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,4 +17,4 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { fromLegacyKueryExpression, fromKueryExpression, fromLiteralExpression, toElasticsearchQuery } from './ast';
|
export * from './ast';
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
// Initialization block
|
// Initialization block
|
||||||
{
|
{
|
||||||
const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options;
|
const { errorOnLuceneSyntax, parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options;
|
||||||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||||
|
@ -26,7 +26,8 @@ start
|
||||||
}
|
}
|
||||||
|
|
||||||
OrQuery
|
OrQuery
|
||||||
= left:AndQuery Or right:OrQuery {
|
= &{ return errorOnLuceneSyntax; } LuceneQuery
|
||||||
|
/ left:AndQuery Or right:OrQuery {
|
||||||
const cursor = [left, right].find(node => node.type === 'cursor');
|
const cursor = [left, right].find(node => node.type === 'cursor');
|
||||||
if (cursor) return cursor;
|
if (cursor) return cursor;
|
||||||
return buildFunctionNode('or', [left, right]);
|
return buildFunctionNode('or', [left, right]);
|
||||||
|
@ -66,7 +67,7 @@ Expression
|
||||||
/ ValueExpression
|
/ ValueExpression
|
||||||
|
|
||||||
FieldRangeExpression
|
FieldRangeExpression
|
||||||
= field:Literal Space* operator:RangeOperator Space* value:(QuotedString / UnquotedLiteral) {
|
= field:Literal Space* operator:RangeOperator Space* value:Literal {
|
||||||
if (value.type === 'cursor') {
|
if (value.type === 'cursor') {
|
||||||
return {
|
return {
|
||||||
...value,
|
...value,
|
||||||
|
@ -172,12 +173,15 @@ Value
|
||||||
|
|
||||||
Or
|
Or
|
||||||
= Space+ 'or'i Space+
|
= Space+ 'or'i Space+
|
||||||
|
/ &{ return errorOnLuceneSyntax; } LuceneOr
|
||||||
|
|
||||||
And
|
And
|
||||||
= Space+ 'and'i Space+
|
= Space+ 'and'i Space+
|
||||||
|
/ &{ return errorOnLuceneSyntax; } LuceneAnd
|
||||||
|
|
||||||
Not
|
Not
|
||||||
= 'not'i Space+
|
= 'not'i Space+
|
||||||
|
/ &{ return errorOnLuceneSyntax; } LuceneNot
|
||||||
|
|
||||||
Literal
|
Literal
|
||||||
= QuotedString / UnquotedLiteral
|
= QuotedString / UnquotedLiteral
|
||||||
|
@ -278,3 +282,109 @@ Space
|
||||||
|
|
||||||
Cursor
|
Cursor
|
||||||
= &{ return parseCursor; } '@kuery-cursor@' { return cursorSymbol; }
|
= &{ return parseCursor; } '@kuery-cursor@' { return cursorSymbol; }
|
||||||
|
|
||||||
|
// Temporary error rules (to help users transition from Lucene... should be removed at some point)
|
||||||
|
|
||||||
|
LuceneOr
|
||||||
|
= Space* '||' Space* {
|
||||||
|
error('LuceneOr');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneAnd
|
||||||
|
= Space* '&&' Space* {
|
||||||
|
error('LuceneAnd');
|
||||||
|
}
|
||||||
|
/ '+' {
|
||||||
|
error('LuceneAnd');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneNot
|
||||||
|
= '-' {
|
||||||
|
error('LuceneNot');
|
||||||
|
}
|
||||||
|
/ '!' {
|
||||||
|
error('LuceneNot');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneQuery
|
||||||
|
= LuceneFieldQuery
|
||||||
|
/ LuceneValue
|
||||||
|
/ LuceneExists
|
||||||
|
|
||||||
|
LuceneFieldQuery
|
||||||
|
= LuceneLiteral Space* ':' Space* LuceneValue
|
||||||
|
|
||||||
|
LuceneValue
|
||||||
|
= LuceneRange
|
||||||
|
/ LuceneWildcard
|
||||||
|
/ LuceneRegex
|
||||||
|
/ LuceneFuzzy
|
||||||
|
/ LuceneProximity
|
||||||
|
/ LuceneBoost
|
||||||
|
|
||||||
|
LuceneExists
|
||||||
|
= '_exists_' Space* ':' Space* LuceneLiteral {
|
||||||
|
error('LuceneExists');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneRange
|
||||||
|
= RangeOperator Space* LuceneLiteral {
|
||||||
|
error('LuceneRange');
|
||||||
|
}
|
||||||
|
/ LuceneRangeStart Space* LuceneLiteral LuceneTo LuceneLiteral LuceneRangeEnd {
|
||||||
|
error('LuceneRange');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneWildcard
|
||||||
|
= (LuceneUnquotedCharacter / '*')* '?' LuceneWildcard* {
|
||||||
|
error('LuceneWildcard');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneRegex
|
||||||
|
= '/' [^/]* '/' {
|
||||||
|
error('LuceneRegex');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneFuzzy
|
||||||
|
= LuceneUnquotedLiteral '~' [0-9]* {
|
||||||
|
error('LuceneFuzzy');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneProximity
|
||||||
|
= QuotedString '~' [0-9]* {
|
||||||
|
error('LuceneProximity');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneBoost
|
||||||
|
= LuceneLiteral '^' [0-9]* {
|
||||||
|
error('LuceneBoost');
|
||||||
|
}
|
||||||
|
|
||||||
|
LuceneLiteral
|
||||||
|
= QuotedString / LuceneUnquotedLiteral
|
||||||
|
|
||||||
|
LuceneUnquotedLiteral
|
||||||
|
= LuceneUnquotedCharacter+
|
||||||
|
|
||||||
|
LuceneUnquotedCharacter
|
||||||
|
= EscapedWhitespace
|
||||||
|
/ EscapedLuceneSpecialCharacter
|
||||||
|
/ !LuceneSpecialCharacter !LuceneKeyword .
|
||||||
|
|
||||||
|
LuceneKeyword
|
||||||
|
= Or / And / LuceneOr / LuceneAnd / LuceneNot / LuceneTo
|
||||||
|
|
||||||
|
EscapedLuceneSpecialCharacter
|
||||||
|
= '\\' LuceneSpecialCharacter { return char; }
|
||||||
|
|
||||||
|
LuceneSpecialCharacter
|
||||||
|
= '+' / '-' / '=' / '>' / '<' / '!' / '(' / ')' / '{' / '}' / '[' / ']' / '^' / '"' / '~' / '*' / '?' / ':' / '\\' / '/'
|
||||||
|
|
||||||
|
LuceneTo
|
||||||
|
= Space+ 'TO' Space+
|
||||||
|
|
||||||
|
LuceneRangeStart
|
||||||
|
= '[' / '{'
|
||||||
|
|
||||||
|
LuceneRangeEnd
|
||||||
|
= ']' / '}'
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query';
|
||||||
import { IndexPattern } from 'ui/index_patterns';
|
import { IndexPattern } from 'ui/index_patterns';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -38,12 +39,21 @@ import { matchPairs } from '../lib/match_pairs';
|
||||||
import { QueryLanguageSwitcher } from './language_switcher';
|
import { QueryLanguageSwitcher } from './language_switcher';
|
||||||
import { SuggestionsComponent } from './typeahead/suggestions_component';
|
import { SuggestionsComponent } from './typeahead/suggestions_component';
|
||||||
|
|
||||||
import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui';
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiFieldText,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiLink,
|
||||||
|
EuiOutsideClickDetector,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui';
|
import { EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui';
|
||||||
|
|
||||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||||
|
import { documentationLinks } from 'ui/documentation_links';
|
||||||
|
import { Toast, toastNotifications } from 'ui/notify';
|
||||||
|
|
||||||
const KEY_CODES = {
|
const KEY_CODES = {
|
||||||
LEFT: 37,
|
LEFT: 37,
|
||||||
|
@ -462,6 +472,8 @@ export class QueryBarUI extends Component<Props, State> {
|
||||||
preventDefault();
|
preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleLuceneSyntaxWarning();
|
||||||
|
|
||||||
if (this.persistedLog) {
|
if (this.persistedLog) {
|
||||||
this.persistedLog.add(this.state.query.query);
|
this.persistedLog.add(this.state.query.query);
|
||||||
}
|
}
|
||||||
|
@ -683,6 +695,56 @@ export class QueryBarUI extends Component<Props, State> {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleLuceneSyntaxWarning() {
|
||||||
|
const { intl, store } = this.props;
|
||||||
|
const { query, language } = this.state.query;
|
||||||
|
if (
|
||||||
|
language === 'kuery' &&
|
||||||
|
!store.get('kibana.luceneSyntaxWarningOptOut') &&
|
||||||
|
doesKueryExpressionHaveLuceneSyntaxError(query)
|
||||||
|
) {
|
||||||
|
const toast = toastNotifications.addWarning({
|
||||||
|
title: intl.formatMessage({
|
||||||
|
id: 'common.ui.queryBar.luceneSyntaxWarningTitle',
|
||||||
|
defaultMessage: 'Lucene syntax warning',
|
||||||
|
}),
|
||||||
|
text: (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="common.ui.queryBar.luceneSyntaxWarningMessage"
|
||||||
|
defaultMessage="It looks like you may be trying to use Lucene query syntax, although you
|
||||||
|
have Kibana Query Language (KQL) selected. Please review the KQL docs {link}."
|
||||||
|
values={{
|
||||||
|
link: (
|
||||||
|
<EuiLink href={documentationLinks.query.kueryQuerySyntax} target="_blank">
|
||||||
|
<FormattedMessage
|
||||||
|
id="common.ui.queryBar.syntaxOptionsDescription.docsLinkText"
|
||||||
|
defaultMessage="here"
|
||||||
|
/>
|
||||||
|
</EuiLink>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton size="s" onClick={() => this.onLuceneSyntaxWarningOptOut(toast)}>
|
||||||
|
Don't show again
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLuceneSyntaxWarningOptOut(toast: Toast) {
|
||||||
|
this.props.store.set('kibana.luceneSyntaxWarningOptOut', true);
|
||||||
|
toastNotifications.remove(toast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue