mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options;
|
||||
const { errorOnLuceneSyntax, parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options;
|
||||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||
|
@ -26,7 +26,8 @@ start
|
|||
}
|
||||
|
||||
OrQuery
|
||||
= left:AndQuery Or right:OrQuery {
|
||||
= &{ return errorOnLuceneSyntax; } LuceneQuery
|
||||
/ left:AndQuery Or right:OrQuery {
|
||||
const cursor = [left, right].find(node => node.type === 'cursor');
|
||||
if (cursor) return cursor;
|
||||
return buildFunctionNode('or', [left, right]);
|
||||
|
@ -66,7 +67,7 @@ Expression
|
|||
/ ValueExpression
|
||||
|
||||
FieldRangeExpression
|
||||
= field:Literal Space* operator:RangeOperator Space* value:(QuotedString / UnquotedLiteral) {
|
||||
= field:Literal Space* operator:RangeOperator Space* value:Literal {
|
||||
if (value.type === 'cursor') {
|
||||
return {
|
||||
...value,
|
||||
|
@ -172,12 +173,15 @@ Value
|
|||
|
||||
Or
|
||||
= Space+ 'or'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneOr
|
||||
|
||||
And
|
||||
= Space+ 'and'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneAnd
|
||||
|
||||
Not
|
||||
= 'not'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneNot
|
||||
|
||||
Literal
|
||||
= QuotedString / UnquotedLiteral
|
||||
|
@ -278,3 +282,109 @@ Space
|
|||
|
||||
Cursor
|
||||
= &{ 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.
|
||||
*/
|
||||
|
||||
import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query';
|
||||
import { IndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
@ -38,12 +39,21 @@ import { matchPairs } from '../lib/match_pairs';
|
|||
import { QueryLanguageSwitcher } from './language_switcher';
|
||||
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
|
||||
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 = {
|
||||
LEFT: 37,
|
||||
|
@ -462,6 +472,8 @@ export class QueryBarUI extends Component<Props, State> {
|
|||
preventDefault();
|
||||
}
|
||||
|
||||
this.handleLuceneSyntaxWarning();
|
||||
|
||||
if (this.persistedLog) {
|
||||
this.persistedLog.add(this.state.query.query);
|
||||
}
|
||||
|
@ -683,6 +695,56 @@ export class QueryBarUI extends Component<Props, State> {
|
|||
</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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue