[7.x] Move @kbn/es-query into data plugin (#51014) (#51783)

This commit is contained in:
Luke Elmers 2019-11-26 16:07:16 -07:00 committed by GitHub
parent 87ad98d8a0
commit a812ee93fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 2030 additions and 2873 deletions

View file

@ -8,6 +8,7 @@ bower_components
/optimize
/built_assets
/html_docs
/src/plugins/data/common/es_query/kuery/ast/_generated_/**
/src/fixtures/vislib/mock_data
/src/legacy/ui/public/angular-bootstrap
/src/legacy/ui/public/flot-charts
@ -19,7 +20,6 @@ bower_components
/src/core/lib/kbn_internal_native_observable
/packages/*/target
/packages/eslint-config-kibana
/packages/kbn-es-query/src/kuery/ast/kuery.js
/packages/kbn-pm/dist
/packages/kbn-plugin-generator/sao_template/template
/packages/kbn-ui-framework/dist

View file

@ -16,7 +16,6 @@
"interpreter": "src/legacy/core_plugins/interpreter",
"kbn": "src/legacy/core_plugins/kibana",
"kbnDocViews": "src/legacy/core_plugins/kbn_doc_views",
"kbnESQuery": "packages/kbn-es-query",
"kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types",
"kibana_react": "src/legacy/core_plugins/kibana_react",
"kibana-react": "src/plugins/kibana_react",

View file

@ -9,5 +9,5 @@ Search for objects
<b>Signature:</b>
```typescript
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage" | "fields">) => Promise<SavedObjectsFindResponsePublic<T>>;
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>>;
```

View file

@ -20,7 +20,7 @@ export declare class SavedObjectsClient
| [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | <code>(objects?: {</code><br/><code> id: string;</code><br/><code> type: string;</code><br/><code> }[]) =&gt; Promise&lt;SavedObjectsBatchResponse&lt;SavedObjectAttributes&gt;&gt;</code> | Returns an array of objects by id |
| [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <code>&lt;T extends SavedObjectAttributes&gt;(type: string, attributes: T, options?: SavedObjectsCreateOptions) =&gt; Promise&lt;SimpleSavedObject&lt;T&gt;&gt;</code> | Persists an object |
| [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | <code>(type: string, id: string) =&gt; Promise&lt;{}&gt;</code> | Deletes an object |
| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <code>&lt;T extends SavedObjectAttributes&gt;(options: Pick&lt;SavedObjectFindOptionsServer, &quot;search&quot; &#124; &quot;filter&quot; &#124; &quot;type&quot; &#124; &quot;searchFields&quot; &#124; &quot;defaultSearchOperator&quot; &#124; &quot;hasReference&quot; &#124; &quot;sortField&quot; &#124; &quot;page&quot; &#124; &quot;perPage&quot; &#124; &quot;fields&quot;&gt;) =&gt; Promise&lt;SavedObjectsFindResponsePublic&lt;T&gt;&gt;</code> | Search for objects |
| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <code>&lt;T extends SavedObjectAttributes&gt;(options: Pick&lt;SavedObjectFindOptionsServer, &quot;search&quot; &#124; &quot;filter&quot; &#124; &quot;type&quot; &#124; &quot;fields&quot; &#124; &quot;searchFields&quot; &#124; &quot;defaultSearchOperator&quot; &#124; &quot;hasReference&quot; &#124; &quot;sortField&quot; &#124; &quot;page&quot; &#124; &quot;perPage&quot;&gt;) =&gt; Promise&lt;SavedObjectsFindResponsePublic&lt;T&gt;&gt;</code> | Search for objects |
| [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <code>&lt;T extends SavedObjectAttributes&gt;(type: string, id: string) =&gt; Promise&lt;SimpleSavedObject&lt;T&gt;&gt;</code> | Fetches a single object |
## Methods

View file

@ -119,7 +119,6 @@
"@kbn/babel-code-parser": "1.0.0",
"@kbn/babel-preset": "1.0.0",
"@kbn/config-schema": "1.0.0",
"@kbn/es-query": "1.0.0",
"@kbn/i18n": "1.0.0",
"@kbn/interpreter": "1.0.0",
"@kbn/pm": "1.0.0",

View file

@ -1,92 +0,0 @@
# kbn-es-query
This module is responsible for generating Elasticsearch queries for Kibana. See explanations below for each of the subdirectories.
## es_query
This folder contains the code that combines Lucene/KQL queries and filters into an Elasticsearch query.
```javascript
buildEsQuery(indexPattern, queries, filters, config)
```
Generates the Elasticsearch query DSL from combining the queries and filters provided.
```javascript
buildQueryFromFilters(filters, indexPattern)
```
Generates the Elasticsearch query DSL from the given filters.
```javascript
luceneStringToDsl(query)
```
Generates the Elasticsearch query DSL from the given Lucene query.
```javascript
migrateFilter(filter, indexPattern)
```
Migrates a filter from a previous version of Elasticsearch to the current version.
```javascript
decorateQuery(query, queryStringOptions)
```
Decorates an Elasticsearch query_string query with the given options.
## filters
This folder contains the code related to Kibana Filter objects, including their definitions, and helper functions to create them. Filters in Kibana always contain a `meta` property which describes which `index` the filter corresponds to, as well as additional data about the specific filter.
The object that is created by each of the following functions corresponds to a Filter object in the `lib` directory (e.g. `PhraseFilter`, `RangeFilter`, etc.)
```javascript
buildExistsFilter(field, indexPattern)
```
Creates a filter (`ExistsFilter`) where the given field exists.
```javascript
buildPhraseFilter(field, value, indexPattern)
```
Creates an filter (`PhraseFilter`) where the given field matches the given value.
```javascript
buildPhrasesFilter(field, params, indexPattern)
```
Creates a filter (`PhrasesFilter`) where the given field matches one or more of the given values. `params` should be an array of values.
```javascript
buildQueryFilter(query, index)
```
Creates a filter (`CustomFilter`) corresponding to a raw Elasticsearch query DSL object.
```javascript
buildRangeFilter(field, params, indexPattern)
```
Creates a filter (`RangeFilter`) where the value for the given field is in the given range. `params` should contain `lt`, `lte`, `gt`, and/or `gte`.
## kuery
This folder contains the code corresponding to generating Elasticsearch queries using the Kibana query language.
In general, you will only need to worry about the following functions from the `ast` folder:
```javascript
fromExpression(expression)
```
Generates an abstract syntax tree corresponding to the raw Kibana query `expression`.
```javascript
toElasticsearchQuery(node, indexPattern)
```
Takes an abstract syntax tree (generated from the previous method) and generates the Elasticsearch query DSL using the given `indexPattern`. Note that if no `indexPattern` is provided, then an Elasticsearch query DSL will still be generated, ignoring things like the index pattern scripted fields, field types, etc.

View file

@ -1,35 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// We can't use common Kibana presets here because of babel versions incompatibility
module.exports = {
env: {
public: {
presets: [
'@kbn/babel-preset/webpack_preset'
],
},
server: {
presets: [
'@kbn/babel-preset/node_preset'
],
},
},
ignore: ['**/__tests__/**/*', '**/*.test.ts', '**/*.test.tsx'],
};

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './src';

View file

@ -1,29 +0,0 @@
{
"name": "@kbn/es-query",
"main": "target/server/index.js",
"browser": "target/public/index.js",
"version": "1.0.0",
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "node scripts/build",
"kbn:bootstrap": "node scripts/build --source-maps",
"kbn:watch": "node scripts/build --source-maps --watch"
},
"dependencies": {
"lodash": "npm:@elastic/lodash@3.10.1-kibana3",
"moment-timezone": "^0.5.27",
"@kbn/i18n": "1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@kbn/expect": "1.0.0",
"del": "^5.1.0",
"getopts": "^2.2.4",
"supports-color": "^7.0.0",
"typescript": "3.5.3"
}
}

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
require('../tasks/build_cli');

View file

@ -1,5 +0,0 @@
{
"meta": {
"index": "logstash-*"
}
}

View file

@ -1,303 +0,0 @@
{
"id": "logstash-*",
"title": "logstash-*",
"fields": [
{
"name": "bytes",
"type": "number",
"esTypes": ["long"],
"count": 10,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ssl",
"type": "boolean",
"esTypes": ["boolean"],
"count": 20,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "@timestamp",
"type": "date",
"esTypes": ["date"],
"count": 30,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "time",
"type": "date",
"esTypes": ["date"],
"count": 30,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "@tags",
"type": "string",
"esTypes": ["keyword"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "utc_time",
"type": "date",
"esTypes": ["date"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "phpmemory",
"type": "number",
"esTypes": ["integer"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ip",
"type": "ip",
"esTypes": ["ip"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "request_body",
"type": "attachment",
"esTypes": ["attachment"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "point",
"type": "geo_point",
"esTypes": ["geo_point"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "area",
"type": "geo_shape",
"esTypes": ["geo_shape"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "hashed",
"type": "murmur3",
"esTypes": ["murmur3"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": false,
"readFromDocValues": false
},
{
"name": "geo.coordinates",
"type": "geo_point",
"esTypes": ["geo_point"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "extension",
"type": "string",
"esTypes": ["keyword"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "machine.os",
"type": "string",
"esTypes": ["text"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "machine.os.raw",
"type": "string",
"esTypes": ["keyword"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true,
"subType": { "multi": { "parent": "machine.os" } }
},
{
"name": "geo.src",
"type": "string",
"esTypes": ["keyword"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "_id",
"type": "string",
"esTypes": ["_id"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "_type",
"type": "string",
"esTypes": ["_type"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "_source",
"type": "_source",
"esTypes": ["_source"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "non-filterable",
"type": "string",
"esTypes": ["text"],
"count": 0,
"scripted": false,
"searchable": false,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "non-sortable",
"type": "string",
"esTypes": ["text"],
"count": 0,
"scripted": false,
"searchable": false,
"aggregatable": false,
"readFromDocValues": false
},
{
"name": "custom_user_field",
"type": "conflict",
"esTypes": ["long", "text"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "script string",
"type": "string",
"count": 0,
"scripted": true,
"script": "'i am a string'",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script number",
"type": "number",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script date",
"type": "date",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "painless",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script murmur3",
"type": "murmur3",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "nestedField.child",
"type": "string",
"esTypes": ["text"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": false,
"readFromDocValues": false,
"subType": { "nested": { "path": "nestedField" } }
},
{
"name": "nestedField.nestedChild.doublyNestedChild",
"type": "string",
"esTypes": ["text"],
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": false,
"readFromDocValues": false,
"subType": { "nested": { "path": "nestedField.nestedChild" } }
}
]
}

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './kuery';

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './kuery';

View file

@ -1,415 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as ast from '../ast';
import expect from '@kbn/expect';
import { nodeTypes } from '../../node_types/index';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
let indexPattern;
describe('kuery AST API', function () {
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('fromKueryExpression', function () {
it('should return a match all "is" function for whitespace', function () {
const expected = nodeTypes.function.buildNode('is', '*', '*');
const actual = ast.fromKueryExpression(' ');
expect(actual).to.eql(expected);
});
it('should return an "is" function with a null field for single literals', function () {
const expected = nodeTypes.function.buildNode('is', null, 'foo');
const actual = ast.fromKueryExpression('foo');
expect(actual).to.eql(expected);
});
it('should ignore extraneous whitespace at the beginning and end of the query', function () {
const expected = nodeTypes.function.buildNode('is', null, 'foo');
const actual = ast.fromKueryExpression(' foo ');
expect(actual).to.eql(expected);
});
it('should not split on whitespace', function () {
const expected = nodeTypes.function.buildNode('is', null, 'foo bar');
const actual = ast.fromKueryExpression('foo bar');
expect(actual).to.eql(expected);
});
it('should support "and" as a binary operator', function () {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]);
const actual = ast.fromKueryExpression('foo and bar');
expect(actual).to.eql(expected);
});
it('should support "or" as a binary operator', function () {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]);
const actual = ast.fromKueryExpression('foo or bar');
expect(actual).to.eql(expected);
});
it('should support negation of queries with a "not" prefix', function () {
const expected = nodeTypes.function.buildNode('not',
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
])
);
const actual = ast.fromKueryExpression('not (foo or bar)');
expect(actual).to.eql(expected);
});
it('"and" should have a higher precedence than "or"', function () {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', null, 'bar'),
nodeTypes.function.buildNode('is', null, 'baz'),
]),
nodeTypes.function.buildNode('is', null, 'qux'),
])
]);
const actual = ast.fromKueryExpression('foo or bar and baz or qux');
expect(actual).to.eql(expected);
});
it('should support grouping to override default precedence', function () {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]),
nodeTypes.function.buildNode('is', null, 'baz'),
]);
const actual = ast.fromKueryExpression('(foo or bar) and baz');
expect(actual).to.eql(expected);
});
it('should support matching against specific fields', function () {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar');
const actual = ast.fromKueryExpression('foo:bar');
expect(actual).to.eql(expected);
});
it('should also not split on whitespace when matching specific fields', function () {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz');
const actual = ast.fromKueryExpression('foo:bar baz');
expect(actual).to.eql(expected);
});
it('should treat quoted values as phrases', function () {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz', true);
const actual = ast.fromKueryExpression('foo:"bar baz"');
expect(actual).to.eql(expected);
});
it('should support a shorthand for matching multiple values against a single field', function () {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'foo', 'bar'),
nodeTypes.function.buildNode('is', 'foo', 'baz'),
]);
const actual = ast.fromKueryExpression('foo:(bar or baz)');
expect(actual).to.eql(expected);
});
it('should support "and" and "not" operators and grouping in the shorthand as well', function () {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'foo', 'bar'),
nodeTypes.function.buildNode('is', 'foo', 'baz'),
]),
nodeTypes.function.buildNode('not',
nodeTypes.function.buildNode('is', 'foo', 'qux')
),
]);
const actual = ast.fromKueryExpression('foo:((bar or baz) and not qux)');
expect(actual).to.eql(expected);
});
it('should support exclusive range operators', function () {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('range', 'bytes', {
gt: 1000,
}),
nodeTypes.function.buildNode('range', 'bytes', {
lt: 8000,
}),
]);
const actual = ast.fromKueryExpression('bytes > 1000 and bytes < 8000');
expect(actual).to.eql(expected);
});
it('should support inclusive range operators', function () {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('range', 'bytes', {
gte: 1000,
}),
nodeTypes.function.buildNode('range', 'bytes', {
lte: 8000,
}),
]);
const actual = ast.fromKueryExpression('bytes >= 1000 and bytes <= 8000');
expect(actual).to.eql(expected);
});
it('should support wildcards in field names', function () {
const expected = nodeTypes.function.buildNode('is', 'machine*', 'osx');
const actual = ast.fromKueryExpression('machine*:osx');
expect(actual).to.eql(expected);
});
it('should support wildcards in values', function () {
const expected = nodeTypes.function.buildNode('is', 'foo', 'ba*');
const actual = ast.fromKueryExpression('foo:ba*');
expect(actual).to.eql(expected);
});
it('should create an exists "is" query when a field is given and "*" is the value', function () {
const expected = nodeTypes.function.buildNode('is', 'foo', '*');
const actual = ast.fromKueryExpression('foo:*');
expect(actual).to.eql(expected);
});
it('should support nested queries indicated by curly braces', () => {
const expected = nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'foo')
);
const actual = ast.fromKueryExpression('nestedField:{ childOfNested: foo }');
expect(actual).to.eql(expected);
});
it('should support nested subqueries and subqueries inside nested queries', () => {
const expected = nodeTypes.function.buildNode(
'and',
[
nodeTypes.function.buildNode('is', 'response', '200'),
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'childOfNested', 'foo'),
nodeTypes.function.buildNode('is', 'childOfNested', 'bar'),
])
)]);
const actual = ast.fromKueryExpression('response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }');
expect(actual).to.eql(expected);
});
it('should support nested sub-queries inside paren groups', () => {
const expected = nodeTypes.function.buildNode(
'and',
[
nodeTypes.function.buildNode('is', 'response', '200'),
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'foo')
),
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'bar')
),
])
]);
const actual = ast.fromKueryExpression('response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )');
expect(actual).to.eql(expected);
});
it('should support nested groups inside other nested groups', () => {
const expected = nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode(
'nested',
'nestedChild',
nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo')
)
);
const actual = ast.fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }');
expect(actual).to.eql(expected);
});
});
describe('fromLiteralExpression', function () {
it('should create literal nodes for unquoted values with correct primitive types', function () {
const stringLiteral = nodeTypes.literal.buildNode('foo');
const booleanFalseLiteral = nodeTypes.literal.buildNode(false);
const booleanTrueLiteral = nodeTypes.literal.buildNode(true);
const numberLiteral = nodeTypes.literal.buildNode(42);
expect(ast.fromLiteralExpression('foo')).to.eql(stringLiteral);
expect(ast.fromLiteralExpression('true')).to.eql(booleanTrueLiteral);
expect(ast.fromLiteralExpression('false')).to.eql(booleanFalseLiteral);
expect(ast.fromLiteralExpression('42')).to.eql(numberLiteral);
});
it('should allow escaping of special characters with a backslash', function () {
const expected = nodeTypes.literal.buildNode('\\():<>"*');
// yo dawg
const actual = ast.fromLiteralExpression('\\\\\\(\\)\\:\\<\\>\\"\\*');
expect(actual).to.eql(expected);
});
it('should support double quoted strings that do not need escapes except for quotes', function () {
const expected = nodeTypes.literal.buildNode('\\():<>"*');
const actual = ast.fromLiteralExpression('"\\():<>\\"*"');
expect(actual).to.eql(expected);
});
it('should support escaped backslashes inside quoted strings', function () {
const expected = nodeTypes.literal.buildNode('\\');
const actual = ast.fromLiteralExpression('"\\\\"');
expect(actual).to.eql(expected);
});
it('should detect wildcards and build wildcard AST nodes', function () {
const expected = nodeTypes.wildcard.buildNode('foo*bar');
const actual = ast.fromLiteralExpression('foo*bar');
expect(actual).to.eql(expected);
});
});
describe('toElasticsearchQuery', function () {
it('should return the given node type\'s ES query representation', function () {
const node = nodeTypes.function.buildNode('exists', 'response');
const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern);
const result = ast.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an empty "and" function for undefined nodes and unknown node types', function () {
const expected = nodeTypes.function.toElasticsearchQuery(nodeTypes.function.buildNode('and', []));
expect(ast.toElasticsearchQuery()).to.eql(expected);
const noTypeNode = nodeTypes.function.buildNode('exists', 'foo');
delete noTypeNode.type;
expect(ast.toElasticsearchQuery(noTypeNode)).to.eql(expected);
const unknownTypeNode = nodeTypes.function.buildNode('exists', 'foo');
unknownTypeNode.type = 'notValid';
expect(ast.toElasticsearchQuery(unknownTypeNode)).to.eql(expected);
});
it('should return the given node type\'s ES query representation including a time zone parameter when one is provided', function () {
const config = { dateFormatTZ: 'America/Phoenix' };
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern, config);
const result = ast.toElasticsearchQuery(node, indexPattern, config);
expect(result).to.eql(expected);
});
});
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);
});
});
});

View file

@ -1,50 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { JsonObject } from '..';
/**
* WARNING: these typings are incomplete
*/
export type KueryNode = any;
export type DslQuery = any;
export interface KueryParseOptions {
helpers: {
[key: string]: any;
};
startRule: string;
allowLeadingWildcards: boolean;
}
export function fromKueryExpression(
expression: string | DslQuery,
parseOptions?: Partial<KueryParseOptions>
): KueryNode;
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: any,
config?: Record<string, any>,
context?: Record<string, any>
): JsonObject;
export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean;

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from '../ast/ast';

View file

@ -1,120 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import * as geoBoundingBox from '../geo_bounding_box';
import { nodeTypes } from '../../node_types';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
let indexPattern;
const params = {
bottomRight: {
lat: 50.73,
lon: -135.35
},
topLeft: {
lat: 73.12,
lon: -174.37
}
};
describe('kuery functions', function () {
describe('geoBoundingBox', function () {
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {
it('should return an "arguments" param', function () {
const result = geoBoundingBox.buildNodeParams('geo', params);
expect(result).to.only.have.keys('arguments');
});
it('arguments should contain the provided fieldName as a literal', function () {
const result = geoBoundingBox.buildNodeParams('geo', params);
const { arguments: [ fieldName ] } = result;
expect(fieldName).to.have.property('type', 'literal');
expect(fieldName).to.have.property('value', 'geo');
});
it('arguments should contain the provided params as named arguments with "lat, lon" string values', function () {
const result = geoBoundingBox.buildNodeParams('geo', params);
const { arguments: [ , ...args ] } = result;
args.map((param) => {
expect(param).to.have.property('type', 'namedArg');
expect(['bottomRight', 'topLeft'].includes(param.name)).to.be(true);
expect(param.value.type).to.be('literal');
const expectedParam = params[param.name];
const expectedLatLon = `${expectedParam.lat}, ${expectedParam.lon}`;
expect(param.value.value).to.be(expectedLatLon);
});
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES geo_bounding_box query representing the given node', function () {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
expect(result).to.have.property('geo_bounding_box');
expect(result.geo_bounding_box.geo).to.have.property('top_left', '73.12, -174.37');
expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35');
});
it('should return an ES geo_bounding_box query without an index pattern', function () {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node);
expect(result).to.have.property('geo_bounding_box');
expect(result.geo_bounding_box.geo).to.have.property('top_left', '73.12, -174.37');
expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35');
});
it('should use the ignore_unmapped parameter', function () {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
expect(result.geo_bounding_box.ignore_unmapped).to.be(true);
});
it('should throw an error for scripted fields', function () {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'script number', params);
expect(geoBoundingBox.toElasticsearchQuery)
.withArgs(node, indexPattern).to.throwException(/Geo bounding box query does not support scripted fields/);
});
it('should use a provided nested context to create a full field name', function () {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).to.have.property('geo_bounding_box');
expect(result.geo_bounding_box).to.have.property('nestedField.geo');
});
});
});
});

View file

@ -1,131 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import * as geoPolygon from '../geo_polygon';
import { nodeTypes } from '../../node_types';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
let indexPattern;
const points = [
{
lat: 69.77,
lon: -171.56
},
{
lat: 50.06,
lon: -169.10
},
{
lat: 69.16,
lon: -125.85
}
];
describe('kuery functions', function () {
describe('geoPolygon', function () {
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {
it('should return an "arguments" param', function () {
const result = geoPolygon.buildNodeParams('geo', points);
expect(result).to.only.have.keys('arguments');
});
it('arguments should contain the provided fieldName as a literal', function () {
const result = geoPolygon.buildNodeParams('geo', points);
const { arguments: [ fieldName ] } = result;
expect(fieldName).to.have.property('type', 'literal');
expect(fieldName).to.have.property('value', 'geo');
});
it('arguments should contain the provided points literal "lat, lon" string values', function () {
const result = geoPolygon.buildNodeParams('geo', points);
const { arguments: [ , ...args ] } = result;
args.forEach((param, index) => {
expect(param).to.have.property('type', 'literal');
const expectedPoint = points[index];
const expectedLatLon = `${expectedPoint.lat}, ${expectedPoint.lon}`;
expect(param.value).to.be(expectedLatLon);
});
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES geo_polygon query representing the given node', function () {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node, indexPattern);
expect(result).to.have.property('geo_polygon');
expect(result.geo_polygon.geo).to.have.property('points');
result.geo_polygon.geo.points.forEach((point, index) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;
expect(point).to.be(expectedLatLon);
});
});
it('should return an ES geo_polygon query without an index pattern', function () {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node);
expect(result).to.have.property('geo_polygon');
expect(result.geo_polygon.geo).to.have.property('points');
result.geo_polygon.geo.points.forEach((point, index) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;
expect(point).to.be(expectedLatLon);
});
});
it('should use the ignore_unmapped parameter', function () {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node, indexPattern);
expect(result.geo_polygon.ignore_unmapped).to.be(true);
});
it('should throw an error for scripted fields', function () {
const node = nodeTypes.function.buildNode('geoPolygon', 'script number', points);
expect(geoPolygon.toElasticsearchQuery)
.withArgs(node, indexPattern).to.throwException(/Geo polygon query does not support scripted fields/);
});
it('should use a provided nested context to create a full field name', function () {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).to.have.property('geo_polygon');
expect(result.geo_polygon).to.have.property('nestedField.geo');
});
});
});
});

View file

@ -1,310 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import * as is from '../is';
import { nodeTypes } from '../../node_types';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
let indexPattern;
describe('kuery functions', function () {
describe('is', function () {
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {
it('fieldName and value should be required arguments', function () {
expect(is.buildNodeParams).to.throwException(/fieldName is a required argument/);
expect(is.buildNodeParams).withArgs('foo').to.throwException(/value is a required argument/);
});
it('arguments should contain the provided fieldName and value as literals', function () {
const { arguments: [fieldName, value] } = is.buildNodeParams('response', 200);
expect(fieldName).to.have.property('type', 'literal');
expect(fieldName).to.have.property('value', 'response');
expect(value).to.have.property('type', 'literal');
expect(value).to.have.property('value', 200);
});
it('should detect wildcards in the provided arguments', function () {
const { arguments: [fieldName, value] } = is.buildNodeParams('machine*', 'win*');
expect(fieldName).to.have.property('type', 'wildcard');
expect(value).to.have.property('type', 'wildcard');
});
it('should default to a non-phrase query', function () {
const { arguments: [, , isPhrase] } = is.buildNodeParams('response', 200);
expect(isPhrase.value).to.be(false);
});
it('should allow specification of a phrase query', function () {
const { arguments: [, , isPhrase] } = is.buildNodeParams('response', 200, true);
expect(isPhrase.value).to.be(true);
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES match_all query when fieldName and value are both "*"', function () {
const expected = {
match_all: {}
};
const node = nodeTypes.function.buildNode('is', '*', '*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an ES multi_match query using default_field when fieldName is null', function () {
const expected = {
multi_match: {
query: 200,
type: 'best_fields',
lenient: true,
}
};
const node = nodeTypes.function.buildNode('is', null, 200);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an ES query_string query using default_field when fieldName is null and value contains a wildcard', function () {
const expected = {
query_string: {
query: 'jpg*',
}
};
const node = nodeTypes.function.buildNode('is', null, 'jpg*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an ES bool query with a sub-query for each field when fieldName is "*"', function () {
const node = nodeTypes.function.buildNode('is', '*', 200);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.have.property('bool');
expect(result.bool.should).to.have.length(indexPattern.fields.length);
});
it('should return an ES exists query when value is "*"', function () {
const expected = {
bool: {
should: [
{ exists: { field: 'extension' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', '*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an ES match query when a concrete fieldName and value are provided', function () {
const expected = {
bool: {
should: [
{ match: { extension: 'jpg' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should return an ES match query when a concrete fieldName and value are provided without an index pattern', function () {
const expected = {
bool: {
should: [
{ match: { extension: 'jpg' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(node);
expect(result).to.eql(expected);
});
it('should support creation of phrase queries', function () {
const expected = {
bool: {
should: [
{ match_phrase: { extension: 'jpg' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg', true);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should create a query_string query for wildcard values', function () {
const expected = {
bool: {
should: [
{
query_string: {
fields: ['extension'],
query: 'jpg*'
}
},
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should support scripted fields', function () {
const node = nodeTypes.function.buildNode('is', 'script string', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result.bool.should[0]).to.have.key('script');
});
it('should support date fields without a dateFormat provided', function () {
const expected = {
bool: {
should: [
{
range: {
'@timestamp': {
gte: '2018-04-03T19:04:17',
lte: '2018-04-03T19:04:17',
}
}
}
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should support date fields with a dateFormat provided', function () {
const config = { dateFormatTZ: 'America/Phoenix' };
const expected = {
bool: {
should: [
{
range: {
'@timestamp': {
gte: '2018-04-03T19:04:17',
lte: '2018-04-03T19:04:17',
time_zone: 'America/Phoenix',
}
}
}
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const result = is.toElasticsearchQuery(node, indexPattern, config);
expect(result).to.eql(expected);
});
it('should use a provided nested context to create a full field name', function () {
const expected = {
bool: {
should: [
{ match: { 'nestedField.extension': 'jpg' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).to.eql(expected);
});
it('should support wildcard field names', function () {
const expected = {
bool: {
should: [
{ match: { extension: 'jpg' } },
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
it('should automatically add a nested query when a wildcard field name covers a nested field', () => {
const expected = {
bool: {
should: [
{
nested: {
path: 'nestedField.nestedChild',
query: {
match: {
'nestedField.nestedChild.doublyNestedChild': 'foo'
}
},
score_mode: 'none'
}
}
],
minimum_should_match: 1
}
};
const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
});
});
});
});

View file

@ -1,88 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { nodeTypes } from '../../../node_types';
import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json';
import { getFullFieldNameNode } from '../../utils/get_full_field_name_node';
let indexPattern;
describe('getFullFieldNameNode', function () {
beforeEach(() => {
indexPattern = indexPatternResponse;
});
it('should return unchanged name node if no nested path is passed in', () => {
const nameNode = nodeTypes.literal.buildNode('notNested');
const result = getFullFieldNameNode(nameNode, indexPattern);
expect(result).to.eql(nameNode);
});
it('should add the nested path if it is valid according to the index pattern', () => {
const nameNode = nodeTypes.literal.buildNode('child');
const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField');
expect(result).to.eql(nodeTypes.literal.buildNode('nestedField.child'));
});
it('should throw an error if a path is provided for a non-nested field', () => {
const nameNode = nodeTypes.literal.buildNode('os');
expect(getFullFieldNameNode)
.withArgs(nameNode, indexPattern, 'machine')
.to
.throwException(/machine.os is not a nested field but is in nested group "machine" in the KQL expression/);
});
it('should throw an error if a nested field is not passed with a path', () => {
const nameNode = nodeTypes.literal.buildNode('nestedField.child');
expect(getFullFieldNameNode)
.withArgs(nameNode, indexPattern)
.to
.throwException(/nestedField.child is a nested field, but is not in a nested group in the KQL expression./);
});
it('should throw an error if a nested field is passed with the wrong path', () => {
const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild');
expect(getFullFieldNameNode)
.withArgs(nameNode, indexPattern, 'nestedField')
.to
// eslint-disable-next-line max-len
.throwException(/Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/);
});
it('should skip error checking for wildcard names', () => {
const nameNode = nodeTypes.wildcard.buildNode('nested*');
const result = getFullFieldNameNode(nameNode, indexPattern);
expect(result).to.eql(nameNode);
});
it('should skip error checking if no index pattern is passed in', () => {
const nameNode = nodeTypes.literal.buildNode('os');
expect(getFullFieldNameNode)
.withArgs(nameNode, null, 'machine')
.to
.not
.throwException();
const result = getFullFieldNameNode(nameNode, null, 'machine');
expect(result).to.eql(nodeTypes.literal.buildNode('machine.os'));
});
});

View file

@ -1,80 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as functionType from '../function';
import _ from 'lodash';
import expect from '@kbn/expect';
import * as isFunction from '../../functions/is';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../../node_types';
describe('kuery node types', function () {
describe('function', function () {
let indexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNode', function () {
it('should return a node representing the given kuery function', function () {
const result = functionType.buildNode('is', 'extension', 'jpg');
expect(result).to.have.property('type', 'function');
expect(result).to.have.property('function', 'is');
expect(result).to.have.property('arguments');
});
});
describe('buildNodeWithArgumentNodes', function () {
it('should return a function node with the given argument list untouched', function () {
const fieldNameLiteral = nodeTypes.literal.buildNode('extension');
const valueLiteral = nodeTypes.literal.buildNode('jpg');
const argumentNodes = [fieldNameLiteral, valueLiteral];
const result = functionType.buildNodeWithArgumentNodes('is', argumentNodes);
expect(result).to.have.property('type', 'function');
expect(result).to.have.property('function', 'is');
expect(result).to.have.property('arguments');
expect(result.arguments).to.be(argumentNodes);
expect(result.arguments).to.eql(argumentNodes);
});
});
describe('toElasticsearchQuery', function () {
it('should return the given function type\'s ES query representation', function () {
const node = functionType.buildNode('is', 'extension', 'jpg');
const expected = isFunction.toElasticsearchQuery(node, indexPattern);
const result = functionType.toElasticsearchQuery(node, indexPattern);
expect(_.isEqual(expected, result)).to.be(true);
});
});
});
});

View file

@ -1,62 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import * as namedArg from '../named_arg';
import { nodeTypes } from '../../node_types';
describe('kuery node types', function () {
describe('named arg', function () {
describe('buildNode', function () {
it('should return a node representing a named argument with the given value', function () {
const result = namedArg.buildNode('fieldName', 'foo');
expect(result).to.have.property('type', 'namedArg');
expect(result).to.have.property('name', 'fieldName');
expect(result).to.have.property('value');
const literalValue = result.value;
expect(literalValue).to.have.property('type', 'literal');
expect(literalValue).to.have.property('value', 'foo');
});
it('should support literal nodes as values', function () {
const value = nodeTypes.literal.buildNode('foo');
const result = namedArg.buildNode('fieldName', value);
expect(result.value).to.be(value);
expect(result.value).to.eql(value);
});
});
describe('toElasticsearchQuery', function () {
it('should return the argument value represented by the given node', function () {
const node = namedArg.buildNode('fieldName', 'foo');
const result = namedArg.toElasticsearchQuery(node);
expect(result).to.be('foo');
});
});
});
});

View file

@ -1,107 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import * as wildcard from '../wildcard';
describe('kuery node types', function () {
describe('wildcard', function () {
describe('buildNode', function () {
it('should accept a string argument representing a wildcard string', function () {
const wildcardValue = `foo${wildcard.wildcardSymbol}bar`;
const result = wildcard.buildNode(wildcardValue);
expect(result).to.have.property('type', 'wildcard');
expect(result).to.have.property('value', wildcardValue);
});
it('should accept and parse a wildcard string', function () {
const result = wildcard.buildNode('foo*bar');
expect(result).to.have.property('type', 'wildcard');
expect(result.value).to.be(`foo${wildcard.wildcardSymbol}bar`);
});
});
describe('toElasticsearchQuery', function () {
it('should return the string representation of the wildcard literal', function () {
const node = wildcard.buildNode('foo*bar');
const result = wildcard.toElasticsearchQuery(node);
expect(result).to.be('foo*bar');
});
});
describe('toQueryStringQuery', function () {
it('should return the string representation of the wildcard literal', function () {
const node = wildcard.buildNode('foo*bar');
const result = wildcard.toQueryStringQuery(node);
expect(result).to.be('foo*bar');
});
it('should escape query_string query special characters other than wildcard', function () {
const node = wildcard.buildNode('+foo*bar');
const result = wildcard.toQueryStringQuery(node);
expect(result).to.be('\\+foo*bar');
});
});
describe('test', function () {
it('should return a boolean indicating whether the string matches the given wildcard node', function () {
const node = wildcard.buildNode('foo*bar');
expect(wildcard.test(node, 'foobar')).to.be(true);
expect(wildcard.test(node, 'foobazbar')).to.be(true);
expect(wildcard.test(node, 'foobar')).to.be(true);
expect(wildcard.test(node, 'fooqux')).to.be(false);
expect(wildcard.test(node, 'bazbar')).to.be(false);
});
it('should return a true even when the string has newlines or tabs', function () {
const node = wildcard.buildNode('foo*bar');
expect(wildcard.test(node, 'foo\nbar')).to.be(true);
expect(wildcard.test(node, 'foo\tbar')).to.be(true);
});
});
describe('hasLeadingWildcard', function () {
it('should determine whether a wildcard node contains a leading wildcard', function () {
const node = wildcard.buildNode('foo*bar');
expect(wildcard.hasLeadingWildcard(node)).to.be(false);
const leadingWildcardNode = wildcard.buildNode('*foobar');
expect(wildcard.hasLeadingWildcard(leadingWildcardNode)).to.be(true);
});
// Lone wildcards become exists queries, so we aren't worried about their performance
it('should not consider a lone wildcard to be a leading wildcard', function () {
const leadingWildcardNode = wildcard.buildNode('*');
expect(wildcard.hasLeadingWildcard(leadingWildcardNode)).to.be(false);
});
});
});
});

View file

@ -1,36 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { getTimeZoneFromSettings } from '../get_time_zone_from_settings';
describe('get timezone from settings', function () {
it('should return the config timezone if the time zone is set', function () {
const result = getTimeZoneFromSettings('America/Chicago');
expect(result).to.eql('America/Chicago');
});
it('should return the system timezone if the time zone is set to "Browser"', function () {
const result = getTimeZoneFromSettings('Browser');
expect(result).to.not.equal('Browser');
});
});

View file

@ -1,133 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { pick, get, reduce, map } from 'lodash';
/** @deprecated
* @see src/plugins/data/public/es_query/filters/phrase_filter.ts
* Code was already moved into src/plugins/data/public.
* This method will be removed after moving 'es_query' into new platform
* */
export const getConvertedValueForField = (field, value) => {
if (typeof value !== 'boolean' && field.type === 'boolean') {
if ([1, 'true'].includes(value)) {
return true;
} else if ([0, 'false'].includes(value)) {
return false;
} else {
throw new Error(`${value} is not a valid boolean value for boolean field ${field.name}`);
}
}
return value;
};
/** @deprecated
* @see src/plugins/data/public/es_query/filters/phrase_filter.ts
* Code was already moved into src/plugins/data/public.
* This method will be removed after moving 'es_query' into new platform
* */
export const buildInlineScriptForPhraseFilter = (scriptedField) => {
// We must wrap painless scripts in a lambda in case they're more than a simple expression
if (scriptedField.lang === 'painless') {
return (
`boolean compare(Supplier s, def v) {return s.get() == v;}` +
`compare(() -> { ${scriptedField.script} }, params.value);`
);
} else {
return `(${scriptedField.script}) == value`;
}
};
/** @deprecated
* @see src/plugins/data/public/es_query/filters/phrase_filter.ts
* Code was already moved into src/plugins/data/public.
* This method will be removed after moving 'es_query' into new platform
* */
export function getPhraseScript(field, value) {
const convertedValue = getConvertedValueForField(field, value);
const script = buildInlineScriptForPhraseFilter(field);
return {
script: {
source: script,
lang: field.lang,
params: {
value: convertedValue,
},
},
};
}
/** @deprecated
* @see src/plugins/data/public/es_query/filters/range_filter.ts
* Code was already moved into src/plugins/data/public.
* This method will be removed after moving 'kuery' into new platform
* */
export function getRangeScript(field, params) {
const operators = {
gt: '>',
gte: '>=',
lte: '<=',
lt: '<',
};
const comparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get() > v}',
gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}',
lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}',
lt: 'boolean lt(Supplier s, def v) {return s.get() < v}',
};
const dateComparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}',
gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}',
lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}',
lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}',
};
const knownParams = pick(params, (val, key) => {
return key in operators;
});
let script = map(knownParams, (val, key) => {
return '(' + field.script + ')' + get(operators, key) + key;
}).join(' && ');
// We must wrap painless scripts in a lambda in case they're more than a simple expression
if (field.lang === 'painless') {
const comp = field.type === 'date' ? dateComparators : comparators;
const currentComparators = reduce(
knownParams,
(acc, val, key) => acc.concat(get(comp, key)),
[]
).join(' ');
const comparisons = map(knownParams, (val, key) => {
return `${key}(() -> { ${field.script} }, params.${key})`;
}).join(' && ');
script = `${currentComparators}${comparisons}`;
}
return {
script: {
source: script,
params: knownParams,
lang: field.lang,
},
};
}

View file

@ -1,28 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import moment from 'moment-timezone';
const detectedTimezone = moment.tz.guess();
export function getTimeZoneFromSettings(dateFormatTZ) {
if (dateFormatTZ === 'Browser') {
return detectedTimezone;
}
return dateFormatTZ;
}

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export * from './get_time_zone_from_settings';

View file

@ -1,103 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const { resolve } = require('path');
const getopts = require('getopts');
const del = require('del');
const supportsColor = require('supports-color');
const { ToolingLog, withProcRunner, pickLevelFromFlags } = require('@kbn/dev-utils');
const ROOT_DIR = resolve(__dirname, '..');
const BUILD_DIR = resolve(ROOT_DIR, 'target');
const padRight = (width, str) =>
str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`;
const unknownFlags = [];
const flags = getopts(process.argv, {
boolean: ['watch', 'help', 'source-maps'],
unknown(name) {
unknownFlags.push(name);
},
});
const log = new ToolingLog({
level: pickLevelFromFlags(flags),
writeTo: process.stdout,
});
if (unknownFlags.length) {
log.error(`Unknown flag(s): ${unknownFlags.join(', ')}`);
flags.help = true;
process.exitCode = 1;
}
if (flags.help) {
log.info(`
Simple build tool for @kbn/es-query package
--watch Run in watch mode
--source-maps Include sourcemaps
--help Show this message
`);
process.exit();
}
withProcRunner(log, async proc => {
log.info('Deleting old output');
await del(BUILD_DIR);
const cwd = ROOT_DIR;
const env = { ...process.env };
if (supportsColor.stdout) {
env.FORCE_COLOR = 'true';
}
log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`);
await Promise.all([
...['public', 'server'].map(subTask =>
proc.run(padRight(12, `babel:${subTask}`), {
cmd: 'babel',
args: [
'src',
'--config-file',
require.resolve('../babel.config.js'),
'--out-dir',
resolve(BUILD_DIR, subTask),
'--extensions',
'.js,.ts,.tsx',
...(flags.watch ? ['--watch'] : ['--quiet']),
...(flags['source-maps'] ? ['--source-map', 'inline'] : []),
],
wait: true,
cwd,
env: {
...env,
BABEL_ENV: subTask,
},
})
),
]);
log.success('Complete');
}).catch(error => {
log.error(error);
process.exit(1);
});

View file

@ -1,11 +0,0 @@
{
"extends": "../../tsconfig.browser.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./target/public"
},
"include": [
"index.d.ts",
"src/**/*.ts"
]
}

View file

@ -1,11 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./target/server"
},
"include": [
"index.d.ts",
"src/**/*.ts"
]
}

View file

@ -846,7 +846,7 @@ export class SavedObjectsClient {
bulkUpdate<T extends SavedObjectAttributes>(objects?: SavedObjectsBulkUpdateObject[]): Promise<SavedObjectsBatchResponse<SavedObjectAttributes>>;
create: <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>>;
delete: (type: string, id: string) => Promise<{}>;
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectsFindOptions, "search" | "filter" | "type" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage" | "fields">) => Promise<SavedObjectsFindResponsePublic<T>>;
find: <T extends SavedObjectAttributes>(options: Pick<SavedObjectsFindOptions, "search" | "filter" | "type" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>>;
get: <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>>;
update<T extends SavedObjectAttributes>(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise<SimpleSavedObject<T>>;
}

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { fromKueryExpression } from '@kbn/es-query';
import { esKuery } from '../../../../../plugins/data/server';
import { validateFilterKueryNode, validateConvertFilterToKueryNode } from './filter_utils';
@ -64,7 +64,7 @@ describe('Filter Utils', () => {
test('Validate a simple filter', () => {
expect(
validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockMappings)
).toEqual(fromKueryExpression('foo.title: "best"'));
).toEqual(esKuery.fromKueryExpression('foo.title: "best"'));
});
test('Assemble filter kuery node saved object attributes with one saved object type', () => {
expect(
@ -74,7 +74,7 @@ describe('Filter Utils', () => {
mockMappings
)
).toEqual(
fromKueryExpression(
esKuery.fromKueryExpression(
'(type: foo and updatedAt: 5678654567) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or foo.description :*)'
)
);
@ -88,7 +88,7 @@ describe('Filter Utils', () => {
mockMappings
)
).toEqual(
fromKueryExpression(
esKuery.fromKueryExpression(
'(type: foo and updatedAt: 5678654567) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or foo.description :*)'
)
);
@ -102,7 +102,7 @@ describe('Filter Utils', () => {
mockMappings
)
).toEqual(
fromKueryExpression(
esKuery.fromKueryExpression(
'((type: bar and updatedAt: 5678654567) or (type: foo and updatedAt: 5678654567)) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or bar.description :*)'
)
);
@ -130,7 +130,7 @@ describe('Filter Utils', () => {
describe('#validateFilterKueryNode', () => {
test('Validate filter query through KueryNode - happy path', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression(
esKuery.fromKueryExpression(
'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)'
),
['foo'],
@ -185,7 +185,7 @@ describe('Filter Utils', () => {
test('Return Error if key is not wrapper by a saved object type', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression(
esKuery.fromKueryExpression(
'updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)'
),
['foo'],
@ -240,7 +240,7 @@ describe('Filter Utils', () => {
test('Return Error if key of a saved object type is not wrapped with attributes', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression(
esKuery.fromKueryExpression(
'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.description :*)'
),
['foo'],
@ -297,7 +297,7 @@ describe('Filter Utils', () => {
test('Return Error if filter is not using an allowed type', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression(
esKuery.fromKueryExpression(
'bar.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)'
),
['foo'],
@ -352,7 +352,7 @@ describe('Filter Utils', () => {
test('Return Error if filter is using an non-existing key in the index patterns of the saved object type', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression(
esKuery.fromKueryExpression(
'foo.updatedAt33: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.header: "best" and (foo.attributes.description: t* or foo.attributes.description :*)'
),
['foo'],
@ -408,7 +408,7 @@ describe('Filter Utils', () => {
test('Return Error if filter is using an non-existing key null key', () => {
const validationObject = validateFilterKueryNode(
fromKueryExpression('foo.attributes.description: hello AND bye'),
esKuery.fromKueryExpression('foo.attributes.description: hello AND bye'),
['foo'],
mockMappings
);

View file

@ -17,18 +17,18 @@
* under the License.
*/
import { fromKueryExpression, KueryNode, nodeTypes } from '@kbn/es-query';
import { get, set } from 'lodash';
import { SavedObjectsErrorHelpers } from './errors';
import { IndexMapping } from '../../mappings';
import { esKuery } from '../../../../../plugins/data/server';
export const validateConvertFilterToKueryNode = (
allowedTypes: string[],
filter: string,
indexMapping: IndexMapping
): KueryNode => {
): esKuery.KueryNode | undefined => {
if (filter && filter.length > 0 && indexMapping) {
const filterKueryNode = fromKueryExpression(filter);
const filterKueryNode = esKuery.fromKueryExpression(filter);
const validationFilterKuery = validateFilterKueryNode(
filterKueryNode,
@ -54,7 +54,7 @@ export const validateConvertFilterToKueryNode = (
validationFilterKuery.forEach(item => {
const path: string[] = item.astPath.length === 0 ? [] : item.astPath.split('.');
const existingKueryNode: KueryNode =
const existingKueryNode: esKuery.KueryNode =
path.length === 0 ? filterKueryNode : get(filterKueryNode, path);
if (item.isSavedObjectAttr) {
existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.split('.')[1];
@ -63,8 +63,8 @@ export const validateConvertFilterToKueryNode = (
set(
filterKueryNode,
path,
nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', 'type', itemType[0]),
esKuery.nodeTypes.function.buildNode('and', [
esKuery.nodeTypes.function.buildNode('is', 'type', itemType[0]),
existingKueryNode,
])
);
@ -79,7 +79,6 @@ export const validateConvertFilterToKueryNode = (
});
return filterKueryNode;
}
return null;
};
interface ValidateFilterKueryNode {
@ -91,41 +90,44 @@ interface ValidateFilterKueryNode {
}
export const validateFilterKueryNode = (
astFilter: KueryNode,
astFilter: esKuery.KueryNode,
types: string[],
indexMapping: IndexMapping,
storeValue: boolean = false,
path: string = 'arguments'
): ValidateFilterKueryNode[] => {
return astFilter.arguments.reduce((kueryNode: string[], ast: KueryNode, index: number) => {
if (ast.arguments) {
const myPath = `${path}.${index}`;
return [
...kueryNode,
...validateFilterKueryNode(
ast,
types,
indexMapping,
ast.type === 'function' && ['is', 'range'].includes(ast.function),
`${myPath}.arguments`
),
];
}
if (storeValue && index === 0) {
const splitPath = path.split('.');
return [
...kueryNode,
{
astPath: splitPath.slice(0, splitPath.length - 1).join('.'),
error: hasFilterKeyError(ast.value, types, indexMapping),
isSavedObjectAttr: isSavedObjectAttr(ast.value, indexMapping),
key: ast.value,
type: getType(ast.value),
},
];
}
return kueryNode;
}, []);
return astFilter.arguments.reduce(
(kueryNode: string[], ast: esKuery.KueryNode, index: number) => {
if (ast.arguments) {
const myPath = `${path}.${index}`;
return [
...kueryNode,
...validateFilterKueryNode(
ast,
types,
indexMapping,
ast.type === 'function' && ['is', 'range'].includes(ast.function),
`${myPath}.arguments`
),
];
}
if (storeValue && index === 0) {
const splitPath = path.split('.');
return [
...kueryNode,
{
astPath: splitPath.slice(0, splitPath.length - 1).join('.'),
error: hasFilterKeyError(ast.value, types, indexMapping),
isSavedObjectAttr: isSavedObjectAttr(ast.value, indexMapping),
key: ast.value,
type: getType(ast.value),
},
];
}
return kueryNode;
},
[]
);
};
const getType = (key: string | undefined | null) =>

View file

@ -1306,8 +1306,7 @@ describe('SavedObjectsRepository', () => {
type: 'foo',
id: '1',
},
indexPattern: undefined,
kueryNode: null,
kueryNode: undefined,
};
await savedObjectsRepository.find(relevantOpts);

View file

@ -448,11 +448,11 @@ export class SavedObjectsRepository {
}
let kueryNode;
try {
kueryNode =
filter && filter !== ''
? validateConvertFilterToKueryNode(allowedTypes, filter, this._mappings)
: null;
if (filter) {
kueryNode = validateConvertFilterToKueryNode(allowedTypes, filter, this._mappings);
}
} catch (e) {
if (e.name === 'KQLSyntaxError') {
throw SavedObjectsErrorHelpers.createBadRequestError('KQLSyntaxError: ' + e.message);

View file

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { toElasticsearchQuery, KueryNode } from '@kbn/es-query';
import { esKuery } from '../../../../../../plugins/data/server';
import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
import { SavedObjectsSchema } from '../../../schema';
@ -91,7 +91,7 @@ interface QueryParams {
searchFields?: string[];
defaultSearchOperator?: string;
hasReference?: HasReferenceQueryParams;
kueryNode?: KueryNode;
kueryNode?: esKuery.KueryNode;
}
/**
@ -111,7 +111,7 @@ export function getQueryParams({
const types = getTypes(mappings, type);
const bool: any = {
filter: [
...(kueryNode != null ? [toElasticsearchQuery(kueryNode)] : []),
...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []),
{
bool: {
must: hasReference

View file

@ -17,13 +17,13 @@
* under the License.
*/
import { KueryNode } from '@kbn/es-query';
import Boom from 'boom';
import { IndexMapping } from '../../../mappings';
import { SavedObjectsSchema } from '../../../schema';
import { getQueryParams } from './query_params';
import { getSortingParams } from './sorting_params';
import { esKuery } from '../../../../../../plugins/data/server';
interface GetSearchDslOptions {
type: string | string[];
@ -37,7 +37,7 @@ interface GetSearchDslOptions {
type: string;
id: string;
};
kueryNode?: KueryNode;
kueryNode?: esKuery.KueryNode;
}
export function getSearchDsl(

View file

@ -18,11 +18,8 @@
*/
import dateMath from '@elastic/datemath';
import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query';
import classNames from 'classnames';
import React, { useState } from 'react';
import {
EuiButton,
EuiFlexGroup,
@ -42,9 +39,9 @@ import {
Query,
PersistedLog,
getQueryLog,
esKuery,
} from '../../../../../../../plugins/data/public';
import { useKibana, toMountPoint } from '../../../../../../../plugins/kibana_react/public';
import { IndexPattern } from '../../../index_patterns';
import { QueryBarInput } from './query_bar_input';
@ -300,7 +297,7 @@ function QueryBarTopRowUI(props: Props) {
language === 'kuery' &&
typeof query === 'string' &&
(!storage || !storage.get('kibana.luceneSyntaxWarningOptOut')) &&
doesKueryExpressionHaveLuceneSyntaxError(query)
esKuery.doesKueryExpressionHaveLuceneSyntaxError(query)
) {
const toast = notifications!.toasts.addWarning({
title: intl.formatMessage({

View file

@ -83,7 +83,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep
sheet: [expression],
extended: {
es: {
filter: esQuery.buildEsQuery(null, query, filters, esQueryConfigs),
filter: esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs),
},
},
time: {

View file

@ -22,7 +22,6 @@ import React, { Component } from 'react';
import * as Rx from 'rxjs';
import { share } from 'rxjs/operators';
import { isEqual, isEmpty, debounce } from 'lodash';
import { fromKueryExpression } from '@kbn/es-query';
import { VisEditorVisualization } from './vis_editor_visualization';
import { Visualization } from './visualization';
import { VisPicker } from './vis_picker';
@ -30,6 +29,7 @@ import { PanelConfig } from './panel_config';
import { createBrushHandler } from '../lib/create_brush_handler';
import { fetchFields } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../../common/extract_index_patterns';
import { esKuery } from '../../../../../plugins/data/public';
import { npStart } from 'ui/new_platform';
@ -88,7 +88,7 @@ export class VisEditor extends Component {
if (filterQuery && filterQuery.language === 'kuery') {
try {
const queryOptions = this.coreContext.uiSettings.get('query:allowLeadingWildcards');
fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions });
esKuery.fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions });
} catch (error) {
return false;
}

View file

@ -49,7 +49,7 @@ export function createVegaRequestHandler({
timeCache.setTimeRange(timeRange);
const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings);
const filtersDsl = esQuery.buildEsQuery(null, query, filters, esQueryConfigs);
const filtersDsl = esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs);
const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings);
return vp.parseAsync();

View file

@ -18,7 +18,7 @@
*/
import { buildEsQuery } from './build_es_query';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { fromKueryExpression, toElasticsearchQuery } from '../kuery';
import { luceneStringToDsl } from './lucene_string_to_dsl';
import { decorateQuery } from './decorate_query';
import { IIndexPattern } from '../../index_patterns';

View file

@ -41,7 +41,7 @@ export interface EsQueryConfig {
* config contains dateformat:tz
*/
export function buildEsQuery(
indexPattern: IIndexPattern | null,
indexPattern: IIndexPattern | undefined,
queries: Query | Query[],
filters: Filter | Filter[],
config: EsQueryConfig = {

View file

@ -31,9 +31,8 @@ describe('filterMatchesIndex', () => {
it('should return true if no index pattern is passed', () => {
const filter = { meta: { index: 'foo', key: 'bar' } } as Filter;
const indexPattern = null;
expect(filterMatchesIndex(filter, indexPattern)).toBe(true);
expect(filterMatchesIndex(filter, undefined)).toBe(true);
});
it('should return true if the filter key matches a field name', () => {

View file

@ -25,7 +25,7 @@ import { Filter } from '../filters';
* this to check if `filter.meta.index` matches `indexPattern.id` instead, but that's a breaking
* change.
*/
export function filterMatchesIndex(filter: Filter, indexPattern: IIndexPattern | null) {
export function filterMatchesIndex(filter: Filter, indexPattern?: IIndexPattern | null) {
if (!filter.meta?.key || !indexPattern) {
return true;
}

View file

@ -54,7 +54,7 @@ const translateToQuery = (filter: Filter) => {
export const buildQueryFromFilters = (
filters: Filter[] = [],
indexPattern: IIndexPattern | null,
indexPattern: IIndexPattern | undefined,
ignoreFilterIfFieldNotInIndex: boolean = false
) => {
filters = filters.filter(filter => filter && !isFilterDisabled(filter));

View file

@ -18,7 +18,7 @@
*/
import { buildQueryFromKuery } from './from_kuery';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { fromKueryExpression, toElasticsearchQuery } from '../kuery';
import { IIndexPattern } from '../../index_patterns';
import { fields } from '../../index_patterns/mocks';
import { Query } from '../../query/types';
@ -30,7 +30,7 @@ describe('build query', () => {
describe('buildQueryFromKuery', () => {
test('should return the parameters of an Elasticsearch bool query', () => {
const result = buildQueryFromKuery(null, [], true);
const result = buildQueryFromKuery(undefined, [], true);
const expected = {
must: [],
filter: [],

View file

@ -17,12 +17,12 @@
* under the License.
*/
import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '@kbn/es-query';
import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '../kuery';
import { IIndexPattern } from '../../index_patterns';
import { Query } from '../../query/types';
export function buildQueryFromKuery(
indexPattern: IIndexPattern | null,
indexPattern: IIndexPattern | undefined,
queries: Query[] = [],
allowLeadingWildcards: boolean = false,
dateFormatTZ?: string
@ -35,22 +35,20 @@ export function buildQueryFromKuery(
}
function buildQuery(
indexPattern: IIndexPattern | null,
indexPattern: IIndexPattern | undefined,
queryASTs: KueryNode[],
config: Record<string, any> = {}
) {
const compoundQueryAST: KueryNode = nodeTypes.function.buildNode('and', queryASTs);
const kueryQuery: Record<string, any> = toElasticsearchQuery(
compoundQueryAST,
indexPattern,
config
);
const compoundQueryAST = nodeTypes.function.buildNode('and', queryASTs);
const kueryQuery = toElasticsearchQuery(compoundQueryAST, indexPattern, config);
return {
must: [],
filter: [],
should: [],
must_not: [],
...kueryQuery.bool,
};
return Object.assign(
{
must: [],
filter: [],
should: [],
must_not: [],
},
kueryQuery.bool
);
}

View file

@ -40,7 +40,7 @@ describe('migrateFilter', function() {
} as unknown) as PhraseFilter;
it('should migrate match filters of type phrase', function() {
const migratedFilter = migrateFilter(oldMatchPhraseFilter, null);
const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined);
expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true);
});
@ -48,7 +48,7 @@ describe('migrateFilter', function() {
it('should not modify the original filter', function() {
const oldMatchPhraseFilterCopy = clone(oldMatchPhraseFilter, true);
migrateFilter(oldMatchPhraseFilter, null);
migrateFilter(oldMatchPhraseFilter, undefined);
expect(isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).toBe(true);
});
@ -57,7 +57,7 @@ describe('migrateFilter', function() {
const originalFilter = {
match_all: {},
} as MatchAllFilter;
const migratedFilter = migrateFilter(originalFilter, null);
const migratedFilter = migrateFilter(originalFilter, undefined);
expect(migratedFilter).toBe(originalFilter);
expect(isEqual(migratedFilter, originalFilter)).toBe(true);

View file

@ -43,7 +43,7 @@ function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter
return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase');
}
export function migrateFilter(filter: Filter, indexPattern: IIndexPattern | null) {
export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) {
if (isMatchPhraseFilter(filter)) {
const fieldName = Object.keys(filter.match)[0];
const params: Record<string, any> = get(filter, ['match', fieldName]);

View file

@ -18,6 +18,7 @@
*/
import * as esQuery from './es_query';
import * as esFilters from './filters';
import * as esKuery from './kuery';
import * as utils from './utils';
export { esFilters, esQuery, utils };
export { esFilters, esQuery, utils, esKuery };

View file

@ -0,0 +1,421 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
fromKueryExpression,
fromLiteralExpression,
toElasticsearchQuery,
doesKueryExpressionHaveLuceneSyntaxError,
} from './ast';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
import { KueryNode } from '../types';
describe('kuery AST API', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('fromKueryExpression', () => {
test('should return a match all "is" function for whitespace', () => {
const expected = nodeTypes.function.buildNode('is', '*', '*');
const actual = fromKueryExpression(' ');
expect(actual).toEqual(expected);
});
test('should return an "is" function with a null field for single literals', () => {
const expected = nodeTypes.function.buildNode('is', null, 'foo');
const actual = fromKueryExpression('foo');
expect(actual).toEqual(expected);
});
test('should ignore extraneous whitespace at the beginning and end of the query', () => {
const expected = nodeTypes.function.buildNode('is', null, 'foo');
const actual = fromKueryExpression(' foo ');
expect(actual).toEqual(expected);
});
test('should not split on whitespace', () => {
const expected = nodeTypes.function.buildNode('is', null, 'foo bar');
const actual = fromKueryExpression('foo bar');
expect(actual).toEqual(expected);
});
test('should support "and" as a binary operator', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]);
const actual = fromKueryExpression('foo and bar');
expect(actual).toEqual(expected);
});
test('should support "or" as a binary operator', () => {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]);
const actual = fromKueryExpression('foo or bar');
expect(actual).toEqual(expected);
});
test('should support negation of queries with a "not" prefix', () => {
const expected = nodeTypes.function.buildNode(
'not',
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
])
);
const actual = fromKueryExpression('not (foo or bar)');
expect(actual).toEqual(expected);
});
test('"and" should have a higher precedence than "or"', () => {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', null, 'bar'),
nodeTypes.function.buildNode('is', null, 'baz'),
]),
nodeTypes.function.buildNode('is', null, 'qux'),
]),
]);
const actual = fromKueryExpression('foo or bar and baz or qux');
expect(actual).toEqual(expected);
});
test('should support grouping to override default precedence', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', null, 'foo'),
nodeTypes.function.buildNode('is', null, 'bar'),
]),
nodeTypes.function.buildNode('is', null, 'baz'),
]);
const actual = fromKueryExpression('(foo or bar) and baz');
expect(actual).toEqual(expected);
});
test('should support matching against specific fields', () => {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar');
const actual = fromKueryExpression('foo:bar');
expect(actual).toEqual(expected);
});
test('should also not split on whitespace when matching specific fields', () => {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz');
const actual = fromKueryExpression('foo:bar baz');
expect(actual).toEqual(expected);
});
test('should treat quoted values as phrases', () => {
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz', true);
const actual = fromKueryExpression('foo:"bar baz"');
expect(actual).toEqual(expected);
});
test('should support a shorthand for matching multiple values against a single field', () => {
const expected = nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'foo', 'bar'),
nodeTypes.function.buildNode('is', 'foo', 'baz'),
]);
const actual = fromKueryExpression('foo:(bar or baz)');
expect(actual).toEqual(expected);
});
test('should support "and" and "not" operators and grouping in the shorthand as well', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'foo', 'bar'),
nodeTypes.function.buildNode('is', 'foo', 'baz'),
]),
nodeTypes.function.buildNode('not', nodeTypes.function.buildNode('is', 'foo', 'qux')),
]);
const actual = fromKueryExpression('foo:((bar or baz) and not qux)');
expect(actual).toEqual(expected);
});
test('should support exclusive range operators', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('range', 'bytes', {
gt: 1000,
}),
nodeTypes.function.buildNode('range', 'bytes', {
lt: 8000,
}),
]);
const actual = fromKueryExpression('bytes > 1000 and bytes < 8000');
expect(actual).toEqual(expected);
});
test('should support inclusive range operators', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('range', 'bytes', {
gte: 1000,
}),
nodeTypes.function.buildNode('range', 'bytes', {
lte: 8000,
}),
]);
const actual = fromKueryExpression('bytes >= 1000 and bytes <= 8000');
expect(actual).toEqual(expected);
});
test('should support wildcards in field names', () => {
const expected = nodeTypes.function.buildNode('is', 'machine*', 'osx');
const actual = fromKueryExpression('machine*:osx');
expect(actual).toEqual(expected);
});
test('should support wildcards in values', () => {
const expected = nodeTypes.function.buildNode('is', 'foo', 'ba*');
const actual = fromKueryExpression('foo:ba*');
expect(actual).toEqual(expected);
});
test('should create an exists "is" query when a field is given and "*" is the value', () => {
const expected = nodeTypes.function.buildNode('is', 'foo', '*');
const actual = fromKueryExpression('foo:*');
expect(actual).toEqual(expected);
});
test('should support nested queries indicated by curly braces', () => {
const expected = nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'foo')
);
const actual = fromKueryExpression('nestedField:{ childOfNested: foo }');
expect(actual).toEqual(expected);
});
test('should support nested subqueries and subqueries inside nested queries', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', 'response', '200'),
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode('is', 'childOfNested', 'foo'),
nodeTypes.function.buildNode('is', 'childOfNested', 'bar'),
])
),
]);
const actual = fromKueryExpression(
'response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }'
);
expect(actual).toEqual(expected);
});
test('should support nested sub-queries inside paren groups', () => {
const expected = nodeTypes.function.buildNode('and', [
nodeTypes.function.buildNode('is', 'response', '200'),
nodeTypes.function.buildNode('or', [
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'foo')
),
nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode('is', 'childOfNested', 'bar')
),
]),
]);
const actual = fromKueryExpression(
'response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )'
);
expect(actual).toEqual(expected);
});
test('should support nested groups inside other nested groups', () => {
const expected = nodeTypes.function.buildNode(
'nested',
'nestedField',
nodeTypes.function.buildNode(
'nested',
'nestedChild',
nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo')
)
);
const actual = fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }');
expect(actual).toEqual(expected);
});
});
describe('fromLiteralExpression', () => {
test('should create literal nodes for unquoted values with correct primitive types', () => {
const stringLiteral = nodeTypes.literal.buildNode('foo');
const booleanFalseLiteral = nodeTypes.literal.buildNode(false);
const booleanTrueLiteral = nodeTypes.literal.buildNode(true);
const numberLiteral = nodeTypes.literal.buildNode(42);
expect(fromLiteralExpression('foo')).toEqual(stringLiteral);
expect(fromLiteralExpression('true')).toEqual(booleanTrueLiteral);
expect(fromLiteralExpression('false')).toEqual(booleanFalseLiteral);
expect(fromLiteralExpression('42')).toEqual(numberLiteral);
});
test('should allow escaping of special characters with a backslash', () => {
const expected = nodeTypes.literal.buildNode('\\():<>"*');
// yo dawg
const actual = fromLiteralExpression('\\\\\\(\\)\\:\\<\\>\\"\\*');
expect(actual).toEqual(expected);
});
test('should support double quoted strings that do not need escapes except for quotes', () => {
const expected = nodeTypes.literal.buildNode('\\():<>"*');
const actual = fromLiteralExpression('"\\():<>\\"*"');
expect(actual).toEqual(expected);
});
test('should support escaped backslashes inside quoted strings', () => {
const expected = nodeTypes.literal.buildNode('\\');
const actual = fromLiteralExpression('"\\\\"');
expect(actual).toEqual(expected);
});
test('should detect wildcards and build wildcard AST nodes', () => {
const expected = nodeTypes.wildcard.buildNode('foo*bar');
const actual = fromLiteralExpression('foo*bar');
expect(actual).toEqual(expected);
});
});
describe('toElasticsearchQuery', () => {
test("should return the given node type's ES query representation", () => {
const node = nodeTypes.function.buildNode('exists', 'response');
const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern);
const result = toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an empty "and" function for undefined nodes and unknown node types', () => {
const expected = nodeTypes.function.toElasticsearchQuery(
nodeTypes.function.buildNode('and', []),
indexPattern
);
expect(toElasticsearchQuery((null as unknown) as KueryNode, undefined)).toEqual(expected);
const noTypeNode = nodeTypes.function.buildNode('exists', 'foo');
delete noTypeNode.type;
expect(toElasticsearchQuery(noTypeNode)).toEqual(expected);
const unknownTypeNode = nodeTypes.function.buildNode('exists', 'foo');
// @ts-ignore
unknownTypeNode.type = 'notValid';
expect(toElasticsearchQuery(unknownTypeNode)).toEqual(expected);
});
test("should return the given node type's ES query representation including a time zone parameter when one is provided", () => {
const config = { dateFormatTZ: 'America/Phoenix' };
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern, config);
const result = toElasticsearchQuery(node, indexPattern, config);
expect(result).toEqual(expected);
});
});
describe('doesKueryExpressionHaveLuceneSyntaxError', () => {
test('should return true for Lucene ranges', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: [1 TO 10]');
expect(result).toEqual(true);
});
test('should return false for KQL ranges', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar < 1');
expect(result).toEqual(false);
});
test('should return true for Lucene exists', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('_exists_: bar');
expect(result).toEqual(true);
});
test('should return false for KQL exists', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar:*');
expect(result).toEqual(false);
});
test('should return true for Lucene wildcards', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba?');
expect(result).toEqual(true);
});
test('should return false for KQL wildcards', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba*');
expect(result).toEqual(false);
});
test('should return true for Lucene regex', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: /ba.*/');
expect(result).toEqual(true);
});
test('should return true for Lucene fuzziness', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba~');
expect(result).toEqual(true);
});
test('should return true for Lucene proximity', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: "ba"~2');
expect(result).toEqual(true);
});
test('should return true for Lucene boosting', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba^2');
expect(result).toEqual(true);
});
test('should return true for Lucene + operator', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('+foo: bar');
expect(result).toEqual(true);
});
test('should return true for Lucene - operators', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('-foo: bar');
expect(result).toEqual(true);
});
test('should return true for Lucene && operators', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar && baz: qux');
expect(result).toEqual(true);
});
test('should return true for Lucene || operators', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar || baz: qux');
expect(result).toEqual(true);
});
test('should return true for mixed KQL/Lucene queries', () => {
const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar and (baz: qux || bag)');
expect(result).toEqual(true);
});
});
});

View file

@ -17,21 +17,44 @@
* under the License.
*/
import _ from 'lodash';
import { nodeTypes } from '../node_types/index';
import { parse as parseKuery } from './kuery';
import { KQLSyntaxError } from '../errors';
import { KQLSyntaxError } from '../kuery_syntax_error';
import { KueryNode, JsonObject, DslQuery, KueryParseOptions } from '../types';
import { IIndexPattern } from '../../../index_patterns/types';
export function fromLiteralExpression(expression, parseOptions) {
parseOptions = {
...parseOptions,
startRule: 'Literal',
};
// @ts-ignore
import { parse as parseKuery } from './_generated_/kuery';
return fromExpression(expression, parseOptions, parseKuery);
}
const fromExpression = (
expression: string | DslQuery,
parseOptions: Partial<KueryParseOptions> = {},
parse: Function = parseKuery
): KueryNode => {
if (typeof expression === 'undefined') {
throw new Error('expression must be a string, got undefined instead');
}
export function fromKueryExpression(expression, parseOptions) {
return parse(expression, { ...parseOptions, helpers: { nodeTypes } });
};
export const fromLiteralExpression = (
expression: string | DslQuery,
parseOptions: Partial<KueryParseOptions> = {}
): KueryNode => {
return fromExpression(
expression,
{
...parseOptions,
startRule: 'Literal',
},
parseKuery
);
};
export const fromKueryExpression = (
expression: string | DslQuery,
parseOptions: Partial<KueryParseOptions> = {}
): KueryNode => {
try {
return fromExpression(expression, parseOptions, parseKuery);
} catch (error) {
@ -41,20 +64,18 @@ export function fromKueryExpression(expression, parseOptions) {
throw error;
}
}
}
};
function fromExpression(expression, parseOptions = {}, parse = parseKuery) {
if (_.isUndefined(expression)) {
throw new Error('expression must be a string, got undefined instead');
export const doesKueryExpressionHaveLuceneSyntaxError = (
expression: string | DslQuery
): boolean => {
try {
fromExpression(expression, { errorOnLuceneSyntax: true }, parseKuery);
return false;
} catch (e) {
return e.message.startsWith('Lucene');
}
parseOptions = {
...parseOptions,
helpers: { nodeTypes },
};
return parse(expression, parseOptions);
}
};
/**
* @params {String} indexPattern
@ -63,19 +84,17 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) {
* IndexPattern isn't required, but if you pass one in, we can be more intelligent
* about how we craft the queries (e.g. scripted fields)
*/
export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) {
export const toElasticsearchQuery = (
node: KueryNode,
indexPattern?: IIndexPattern,
config?: Record<string, any>,
context?: Record<string, any>
): JsonObject => {
if (!node || !node.type || !nodeTypes[node.type]) {
return toElasticsearchQuery(nodeTypes.function.buildNode('and', []));
return toElasticsearchQuery(nodeTypes.function.buildNode('and', []), indexPattern);
}
return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config, context);
}
const nodeType = (nodeTypes[node.type] as unknown) as any;
export function doesKueryExpressionHaveLuceneSyntaxError(expression) {
try {
fromExpression(expression, { errorOnLuceneSyntax: true }, parseKuery);
return false;
} catch (e) {
return (e.message.startsWith('Lucene'));
}
}
return nodeType.toElasticsearchQuery(node, indexPattern, config, context);
};

View file

@ -17,43 +17,53 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as and from '../and';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
import * as ast from '../ast';
let indexPattern;
// @ts-ignore
import * as and from './and';
const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx');
const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg');
describe('kuery functions', function () {
describe('and', function () {
describe('kuery functions', () => {
describe('and', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
it('arguments should contain the unmodified child nodes', function () {
describe('buildNodeParams', () => {
test('arguments should contain the unmodified child nodes', () => {
const result = and.buildNodeParams([childNode1, childNode2]);
const { arguments: [ actualChildNode1, actualChildNode2 ] } = result;
expect(actualChildNode1).to.be(childNode1);
expect(actualChildNode2).to.be(childNode2);
const {
arguments: [actualChildNode1, actualChildNode2],
} = result;
expect(actualChildNode1).toBe(childNode1);
expect(actualChildNode2).toBe(childNode2);
});
});
describe('toElasticsearchQuery', function () {
it('should wrap subqueries in an ES bool query\'s filter clause', function () {
describe('toElasticsearchQuery', () => {
test("should wrap subqueries in an ES bool query's filter clause", () => {
const node = nodeTypes.function.buildNode('and', [childNode1, childNode2]);
const result = and.toElasticsearchQuery(node, indexPattern);
expect(result).to.only.have.keys('bool');
expect(result.bool).to.only.have.keys('filter');
expect(result.bool.filter).to.eql(
[childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern))
expect(result).toHaveProperty('bool');
expect(Object.keys(result).length).toBe(1);
expect(result.bool).toHaveProperty('filter');
expect(Object.keys(result.bool).length).toBe(1);
expect(result.bool.filter).toEqual(
[childNode1, childNode2].map(childNode =>
ast.toElasticsearchQuery(childNode, indexPattern)
)
);
});
});

View file

@ -17,67 +17,73 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as exists from '../exists';
import { nodeTypes } from '../../node_types';
import _ from 'lodash';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
// @ts-ignore
import * as exists from './exists';
let indexPattern;
describe('kuery functions', function () {
describe('exists', function () {
describe('kuery functions', () => {
describe('exists', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
it('should return a single "arguments" param', function () {
describe('buildNodeParams', () => {
test('should return a single "arguments" param', () => {
const result = exists.buildNodeParams('response');
expect(result).to.only.have.key('arguments');
expect(result).toHaveProperty('arguments');
expect(Object.keys(result).length).toBe(1);
});
it('arguments should contain the provided fieldName as a literal', function () {
const { arguments: [ arg ] } = exists.buildNodeParams('response');
expect(arg).to.have.property('type', 'literal');
expect(arg).to.have.property('value', 'response');
test('arguments should contain the provided fieldName as a literal', () => {
const {
arguments: [arg],
} = exists.buildNodeParams('response');
expect(arg).toHaveProperty('type', 'literal');
expect(arg).toHaveProperty('value', 'response');
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES exists query', function () {
describe('toElasticsearchQuery', () => {
test('should return an ES exists query', () => {
const expected = {
exists: { field: 'response' }
exists: { field: 'response' },
};
const existsNode = nodeTypes.function.buildNode('exists', 'response');
const result = exists.toElasticsearchQuery(existsNode, indexPattern);
expect(_.isEqual(expected, result)).to.be(true);
expect(expected).toEqual(result);
});
it('should return an ES exists query without an index pattern', function () {
test('should return an ES exists query without an index pattern', () => {
const expected = {
exists: { field: 'response' }
exists: { field: 'response' },
};
const existsNode = nodeTypes.function.buildNode('exists', 'response');
const result = exists.toElasticsearchQuery(existsNode);
expect(_.isEqual(expected, result)).to.be(true);
expect(expected).toEqual(result);
});
it('should throw an error for scripted fields', function () {
test('should throw an error for scripted fields', () => {
const existsNode = nodeTypes.function.buildNode('exists', 'script string');
expect(exists.toElasticsearchQuery)
.withArgs(existsNode, indexPattern).to.throwException(/Exists query does not support scripted fields/);
expect(() => exists.toElasticsearchQuery(existsNode, indexPattern)).toThrowError(
/Exists query does not support scripted fields/
);
});
it('should use a provided nested context to create a full field name', function () {
test('should use a provided nested context to create a full field name', () => {
const expected = {
exists: { field: 'nestedField.response' }
exists: { field: 'nestedField.response' },
};
const existsNode = nodeTypes.function.buildNode('exists', 'response');
const result = exists.toElasticsearchQuery(
existsNode,
@ -85,7 +91,8 @@ describe('kuery functions', function () {
{},
{ nested: { path: 'nestedField' } }
);
expect(_.isEqual(expected, result)).to.be(true);
expect(expected).toEqual(result);
});
});
});

View file

@ -0,0 +1,133 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { get } from 'lodash';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
// @ts-ignore
import * as geoBoundingBox from './geo_bounding_box';
const params = {
bottomRight: {
lat: 50.73,
lon: -135.35,
},
topLeft: {
lat: 73.12,
lon: -174.37,
},
};
describe('kuery functions', () => {
describe('geoBoundingBox', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', () => {
test('should return an "arguments" param', () => {
const result = geoBoundingBox.buildNodeParams('geo', params);
expect(result).toHaveProperty('arguments');
expect(Object.keys(result).length).toBe(1);
});
test('arguments should contain the provided fieldName as a literal', () => {
const result = geoBoundingBox.buildNodeParams('geo', params);
const {
arguments: [fieldName],
} = result;
expect(fieldName).toHaveProperty('type', 'literal');
expect(fieldName).toHaveProperty('value', 'geo');
});
test('arguments should contain the provided params as named arguments with "lat, lon" string values', () => {
const result = geoBoundingBox.buildNodeParams('geo', params);
const {
arguments: [, ...args],
} = result;
args.map((param: any) => {
expect(param).toHaveProperty('type', 'namedArg');
expect(['bottomRight', 'topLeft'].includes(param.name)).toBe(true);
expect(param.value.type).toBe('literal');
const { lat, lon } = get(params, param.name);
expect(param.value.value).toBe(`${lat}, ${lon}`);
});
});
});
describe('toElasticsearchQuery', () => {
test('should return an ES geo_bounding_box query representing the given node', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
expect(result).toHaveProperty('geo_bounding_box');
expect(result.geo_bounding_box.geo).toHaveProperty('top_left', '73.12, -174.37');
expect(result.geo_bounding_box.geo).toHaveProperty('bottom_right', '50.73, -135.35');
});
test('should return an ES geo_bounding_box query without an index pattern', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node);
expect(result).toHaveProperty('geo_bounding_box');
expect(result.geo_bounding_box.geo).toHaveProperty('top_left', '73.12, -174.37');
expect(result.geo_bounding_box.geo).toHaveProperty('bottom_right', '50.73, -135.35');
});
test('should use the ignore_unmapped parameter', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
expect(result.geo_bounding_box.ignore_unmapped).toBe(true);
});
test('should throw an error for scripted fields', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'script number', params);
expect(() => geoBoundingBox.toElasticsearchQuery(node, indexPattern)).toThrowError(
/Geo bounding box query does not support scripted fields/
);
});
test('should use a provided nested context to create a full field name', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).toHaveProperty('geo_bounding_box');
expect(result.geo_bounding_box['nestedField.geo']).toBeDefined();
});
});
});
});

View file

@ -0,0 +1,143 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
// @ts-ignore
import * as geoPolygon from './geo_polygon';
const points = [
{
lat: 69.77,
lon: -171.56,
},
{
lat: 50.06,
lon: -169.1,
},
{
lat: 69.16,
lon: -125.85,
},
];
describe('kuery functions', () => {
describe('geoPolygon', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', () => {
test('should return an "arguments" param', () => {
const result = geoPolygon.buildNodeParams('geo', points);
expect(result).toHaveProperty('arguments');
expect(Object.keys(result).length).toBe(1);
});
test('arguments should contain the provided fieldName as a literal', () => {
const result = geoPolygon.buildNodeParams('geo', points);
const {
arguments: [fieldName],
} = result;
expect(fieldName).toHaveProperty('type', 'literal');
expect(fieldName).toHaveProperty('value', 'geo');
});
test('arguments should contain the provided points literal "lat, lon" string values', () => {
const result = geoPolygon.buildNodeParams('geo', points);
const {
arguments: [, ...args],
} = result;
args.forEach((param: any, index: number) => {
const expectedPoint = points[index];
const expectedLatLon = `${expectedPoint.lat}, ${expectedPoint.lon}`;
expect(param).toHaveProperty('type', 'literal');
expect(param.value).toBe(expectedLatLon);
});
});
});
describe('toElasticsearchQuery', () => {
test('should return an ES geo_polygon query representing the given node', () => {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node, indexPattern);
expect(result).toHaveProperty('geo_polygon');
expect(result.geo_polygon.geo).toHaveProperty('points');
result.geo_polygon.geo.points.forEach((point: any, index: number) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;
expect(point).toBe(expectedLatLon);
});
});
test('should return an ES geo_polygon query without an index pattern', () => {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node);
expect(result).toHaveProperty('geo_polygon');
expect(result.geo_polygon.geo).toHaveProperty('points');
result.geo_polygon.geo.points.forEach((point: any, index: number) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;
expect(point).toBe(expectedLatLon);
});
});
test('should use the ignore_unmapped parameter', () => {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(node, indexPattern);
expect(result.geo_polygon.ignore_unmapped).toBe(true);
});
test('should throw an error for scripted fields', () => {
const node = nodeTypes.function.buildNode('geoPolygon', 'script number', points);
expect(() => geoPolygon.toElasticsearchQuery(node, indexPattern)).toThrowError(
/Geo polygon query does not support scripted fields/
);
});
test('should use a provided nested context to create a full field name', () => {
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
const result = geoPolygon.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).toHaveProperty('geo_polygon');
expect(result.geo_polygon['nestedField.geo']).toBeDefined();
});
});
});
});

View file

@ -17,20 +17,22 @@
* under the License.
*/
import _ from 'lodash';
import * as ast from '../ast';
import * as literal from '../node_types/literal';
import * as wildcard from '../node_types/wildcard';
import { getPhraseScript } from '../../utils/filters';
import { get, isUndefined } from 'lodash';
import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
import * as ast from '../ast';
import * as literal from '../node_types/literal';
import * as wildcard from '../node_types/wildcard';
export function buildNodeParams(fieldName, value, isPhrase = false) {
if (_.isUndefined(fieldName)) {
if (isUndefined(fieldName)) {
throw new Error('fieldName is a required argument');
}
if (_.isUndefined(value)) {
if (isUndefined(value)) {
throw new Error('value is a required argument');
}
const fieldNode = typeof fieldName === 'string' ? ast.fromLiteralExpression(fieldName) : literal.buildNode(fieldName);
@ -45,7 +47,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con
const { arguments: [fieldNameArg, valueArg, isPhraseArg] } = node;
const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined);
const fieldName = ast.toElasticsearchQuery(fullFieldNameArg);
const value = !_.isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg;
const value = !isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg;
const type = isPhraseArg.value ? 'phrase' : 'best_fields';
if (fullFieldNameArg.value === null) {
if (valueArg.type === 'wildcard') {
@ -94,7 +96,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con
// users handle this themselves so we automatically add nested queries in this scenario.
if (
!(fullFieldNameArg.type === 'wildcard')
|| !_.get(field, 'subType.nested')
|| !get(field, 'subType.nested')
|| context.nested
) {
return query;

View file

@ -0,0 +1,305 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
// @ts-ignore
import * as is from './is';
import { IIndexPattern } from '../../../index_patterns';
describe('kuery functions', () => {
describe('is', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', () => {
test('fieldName and value should be required arguments', () => {
expect(() => is.buildNodeParams()).toThrowError(/fieldName is a required argument/);
expect(() => is.buildNodeParams('foo')).toThrowError(/value is a required argument/);
});
test('arguments should contain the provided fieldName and value as literals', () => {
const {
arguments: [fieldName, value],
} = is.buildNodeParams('response', 200);
expect(fieldName).toHaveProperty('type', 'literal');
expect(fieldName).toHaveProperty('value', 'response');
expect(value).toHaveProperty('type', 'literal');
expect(value).toHaveProperty('value', 200);
});
test('should detect wildcards in the provided arguments', () => {
const {
arguments: [fieldName, value],
} = is.buildNodeParams('machine*', 'win*');
expect(fieldName).toHaveProperty('type', 'wildcard');
expect(value).toHaveProperty('type', 'wildcard');
});
test('should default to a non-phrase query', () => {
const {
arguments: [, , isPhrase],
} = is.buildNodeParams('response', 200);
expect(isPhrase.value).toBe(false);
});
test('should allow specification of a phrase query', () => {
const {
arguments: [, , isPhrase],
} = is.buildNodeParams('response', 200, true);
expect(isPhrase.value).toBe(true);
});
});
describe('toElasticsearchQuery', () => {
test('should return an ES match_all query when fieldName and value are both "*"', () => {
const expected = {
match_all: {},
};
const node = nodeTypes.function.buildNode('is', '*', '*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an ES multi_match query using default_field when fieldName is null', () => {
const expected = {
multi_match: {
query: 200,
type: 'best_fields',
lenient: true,
},
};
const node = nodeTypes.function.buildNode('is', null, 200);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an ES query_string query using default_field when fieldName is null and value contains a wildcard', () => {
const expected = {
query_string: {
query: 'jpg*',
},
};
const node = nodeTypes.function.buildNode('is', null, 'jpg*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an ES bool query with a sub-query for each field when fieldName is "*"', () => {
const node = nodeTypes.function.buildNode('is', '*', 200);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toHaveProperty('bool');
expect(result.bool.should.length).toBe(indexPattern.fields.length);
});
test('should return an ES exists query when value is "*"', () => {
const expected = {
bool: {
should: [{ exists: { field: 'extension' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', '*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an ES match query when a concrete fieldName and value are provided', () => {
const expected = {
bool: {
should: [{ match: { extension: 'jpg' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should return an ES match query when a concrete fieldName and value are provided without an index pattern', () => {
const expected = {
bool: {
should: [{ match: { extension: 'jpg' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(node);
expect(result).toEqual(expected);
});
test('should support creation of phrase queries', () => {
const expected = {
bool: {
should: [{ match_phrase: { extension: 'jpg' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg', true);
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should create a query_string query for wildcard values', () => {
const expected = {
bool: {
should: [
{
query_string: {
fields: ['extension'],
query: 'jpg*',
},
},
],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should support scripted fields', () => {
const node = nodeTypes.function.buildNode('is', 'script string', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result.bool.should[0]).toHaveProperty('script');
});
test('should support date fields without a dateFormat provided', () => {
const expected = {
bool: {
should: [
{
range: {
'@timestamp': {
gte: '2018-04-03T19:04:17',
lte: '2018-04-03T19:04:17',
},
},
},
],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should support date fields with a dateFormat provided', () => {
const config = { dateFormatTZ: 'America/Phoenix' };
const expected = {
bool: {
should: [
{
range: {
'@timestamp': {
gte: '2018-04-03T19:04:17',
lte: '2018-04-03T19:04:17',
time_zone: 'America/Phoenix',
},
},
},
],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
const result = is.toElasticsearchQuery(node, indexPattern, config);
expect(result).toEqual(expected);
});
test('should use a provided nested context to create a full field name', () => {
const expected = {
bool: {
should: [{ match: { 'nestedField.extension': 'jpg' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
const result = is.toElasticsearchQuery(
node,
indexPattern,
{},
{ nested: { path: 'nestedField' } }
);
expect(result).toEqual(expected);
});
test('should support wildcard field names', () => {
const expected = {
bool: {
should: [{ match: { extension: 'jpg' } }],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
test('should automatically add a nested query when a wildcard field name covers a nested field', () => {
const expected = {
bool: {
should: [
{
nested: {
path: 'nestedField.nestedChild',
query: {
match: {
'nestedField.nestedChild.doublyNestedChild': 'foo',
},
},
score_mode: 'none',
},
},
],
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);
expect(result).toEqual(expected);
});
});
});
});

View file

@ -17,52 +17,60 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as nested from '../nested';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
let indexPattern;
import * as ast from '../ast';
// @ts-ignore
import * as nested from './nested';
const childNode = nodeTypes.function.buildNode('is', 'child', 'foo');
describe('kuery functions', function () {
describe('nested', function () {
describe('kuery functions', () => {
describe('nested', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
it('arguments should contain the unmodified child nodes', function () {
describe('buildNodeParams', () => {
test('arguments should contain the unmodified child nodes', () => {
const result = nested.buildNodeParams('nestedField', childNode);
const { arguments: [ resultPath, resultChildNode ] } = result;
expect(ast.toElasticsearchQuery(resultPath)).to.be('nestedField');
expect(resultChildNode).to.be(childNode);
const {
arguments: [resultPath, resultChildNode],
} = result;
expect(ast.toElasticsearchQuery(resultPath)).toBe('nestedField');
expect(resultChildNode).toBe(childNode);
});
});
describe('toElasticsearchQuery', function () {
it('should wrap subqueries in an ES nested query', function () {
describe('toElasticsearchQuery', () => {
test('should wrap subqueries in an ES nested query', () => {
const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode);
const result = nested.toElasticsearchQuery(node, indexPattern);
expect(result).to.only.have.keys('nested');
expect(result.nested.path).to.be('nestedField');
expect(result.nested.score_mode).to.be('none');
expect(result).toHaveProperty('nested');
expect(Object.keys(result).length).toBe(1);
expect(result.nested.path).toBe('nestedField');
expect(result.nested.score_mode).toBe('none');
});
it('should pass the nested path to subqueries so the full field name can be used', function () {
test('should pass the nested path to subqueries so the full field name can be used', () => {
const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode);
const result = nested.toElasticsearchQuery(node, indexPattern);
const expectedSubQuery = ast.toElasticsearchQuery(
nodeTypes.function.buildNode('is', 'nestedField.child', 'foo')
);
expect(result.nested.query).to.eql(expectedSubQuery);
});
expect(result.nested.query).toEqual(expectedSubQuery);
});
});
});
});

View file

@ -17,44 +17,50 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as not from '../not';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
let indexPattern;
import * as ast from '../ast';
// @ts-ignore
import * as not from './not';
const childNode = nodeTypes.function.buildNode('is', 'extension', 'jpg');
describe('kuery functions', function () {
describe('not', function () {
describe('kuery functions', () => {
describe('not', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
describe('buildNodeParams', () => {
test('arguments should contain the unmodified child node', () => {
const {
arguments: [actualChild],
} = not.buildNodeParams(childNode);
it('arguments should contain the unmodified child node', function () {
const { arguments: [ actualChild ] } = not.buildNodeParams(childNode);
expect(actualChild).to.be(childNode);
expect(actualChild).toBe(childNode);
});
});
describe('toElasticsearchQuery', function () {
it('should wrap a subquery in an ES bool query\'s must_not clause', function () {
describe('toElasticsearchQuery', () => {
test("should wrap a subquery in an ES bool query's must_not clause", () => {
const node = nodeTypes.function.buildNode('not', childNode);
const result = not.toElasticsearchQuery(node, indexPattern);
expect(result).to.only.have.keys('bool');
expect(result.bool).to.only.have.keys('must_not');
expect(result.bool.must_not).to.eql(ast.toElasticsearchQuery(childNode, indexPattern));
});
expect(result).toHaveProperty('bool');
expect(Object.keys(result).length).toBe(1);
expect(result.bool).toHaveProperty('must_not');
expect(Object.keys(result.bool).length).toBe(1);
expect(result.bool.must_not).toEqual(ast.toElasticsearchQuery(childNode, indexPattern));
});
});
});
});

View file

@ -17,56 +17,61 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as or from '../or';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
let indexPattern;
import * as ast from '../ast';
// @ts-ignore
import * as or from './or';
const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx');
const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg');
describe('kuery functions', function () {
describe('or', function () {
describe('kuery functions', () => {
describe('or', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
it('arguments should contain the unmodified child nodes', function () {
describe('buildNodeParams', () => {
test('arguments should contain the unmodified child nodes', () => {
const result = or.buildNodeParams([childNode1, childNode2]);
const { arguments: [ actualChildNode1, actualChildNode2 ] } = result;
expect(actualChildNode1).to.be(childNode1);
expect(actualChildNode2).to.be(childNode2);
});
const {
arguments: [actualChildNode1, actualChildNode2],
} = result;
expect(actualChildNode1).toBe(childNode1);
expect(actualChildNode2).toBe(childNode2);
});
});
describe('toElasticsearchQuery', function () {
it('should wrap subqueries in an ES bool query\'s should clause', function () {
describe('toElasticsearchQuery', () => {
test("should wrap subqueries in an ES bool query's should clause", () => {
const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]);
const result = or.toElasticsearchQuery(node, indexPattern);
expect(result).to.only.have.keys('bool');
expect(result.bool).to.have.keys('should');
expect(result.bool.should).to.eql(
[childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern))
expect(result).toHaveProperty('bool');
expect(Object.keys(result).length).toBe(1);
expect(result.bool).toHaveProperty('should');
expect(result.bool.should).toEqual(
[childNode1, childNode2].map(childNode =>
ast.toElasticsearchQuery(childNode, indexPattern)
)
);
});
it('should require one of the clauses to match', function () {
test('should require one of the clauses to match', () => {
const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]);
const result = or.toElasticsearchQuery(node, indexPattern);
expect(result.bool).to.have.property('minimum_should_match', 1);
expect(result.bool).toHaveProperty('minimum_should_match', 1);
});
});
});
});

View file

@ -20,7 +20,7 @@
import _ from 'lodash';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { getRangeScript } from '../../utils/filters';
import { getRangeScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';

View file

@ -17,53 +17,57 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as range from '../range';
import { nodeTypes } from '../../node_types';
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
import { get } from 'lodash';
import { nodeTypes } from '../node_types';
import { fields } from '../../../index_patterns/mocks';
import { IIndexPattern } from '../../../index_patterns';
import { RangeFilterParams } from '../../filters';
let indexPattern;
describe('kuery functions', function () {
describe('range', function () {
// @ts-ignore
import * as range from './range';
describe('kuery functions', () => {
describe('range', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNodeParams', function () {
it('arguments should contain the provided fieldName as a literal', function () {
describe('buildNodeParams', () => {
test('arguments should contain the provided fieldName as a literal', () => {
const result = range.buildNodeParams('bytes', { gt: 1000, lt: 8000 });
const { arguments: [fieldName] } = result;
const {
arguments: [fieldName],
} = result;
expect(fieldName).to.have.property('type', 'literal');
expect(fieldName).to.have.property('value', 'bytes');
expect(fieldName).toHaveProperty('type', 'literal');
expect(fieldName).toHaveProperty('value', 'bytes');
});
it('arguments should contain the provided params as named arguments', function () {
const givenParams = { gt: 1000, lt: 8000, format: 'epoch_millis' };
test('arguments should contain the provided params as named arguments', () => {
const givenParams: RangeFilterParams = { gt: 1000, lt: 8000, format: 'epoch_millis' };
const result = range.buildNodeParams('bytes', givenParams);
const { arguments: [, ...params] } = result;
const {
arguments: [, ...params],
} = result;
expect(params).to.be.an('array');
expect(params).to.not.be.empty();
expect(Array.isArray(params)).toBeTruthy();
expect(params.length).toBeGreaterThan(1);
params.map((param) => {
expect(param).to.have.property('type', 'namedArg');
expect(['gt', 'lt', 'format'].includes(param.name)).to.be(true);
expect(param.value.type).to.be('literal');
expect(param.value.value).to.be(givenParams[param.name]);
params.map((param: any) => {
expect(param).toHaveProperty('type', 'namedArg');
expect(['gt', 'lt', 'format'].includes(param.name)).toBe(true);
expect(param.value.type).toBe('literal');
expect(param.value.value).toBe(get(givenParams, param.name));
});
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES range query for the node\'s field and params', function () {
describe('toElasticsearchQuery', () => {
test("should return an ES range query for the node's field and params", () => {
const expected = {
bool: {
should: [
@ -71,21 +75,21 @@ describe('kuery functions', function () {
range: {
bytes: {
gt: 1000,
lt: 8000
}
}
}
lt: 8000,
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
const result = range.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should return an ES range query without an index pattern', function () {
test('should return an ES range query without an index pattern', () => {
const expected = {
bool: {
should: [
@ -93,21 +97,22 @@ describe('kuery functions', function () {
range: {
bytes: {
gt: 1000,
lt: 8000
}
}
}
lt: 8000,
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
const result = range.toElasticsearchQuery(node);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should support wildcard field names', function () {
test('should support wildcard field names', () => {
const expected = {
bool: {
should: [
@ -115,27 +120,29 @@ describe('kuery functions', function () {
range: {
bytes: {
gt: 1000,
lt: 8000
}
}
}
lt: 8000,
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', 'byt*', { gt: 1000, lt: 8000 });
const result = range.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should support scripted fields', function () {
test('should support scripted fields', () => {
const node = nodeTypes.function.buildNode('range', 'script number', { gt: 1000, lt: 8000 });
const result = range.toElasticsearchQuery(node, indexPattern);
expect(result.bool.should[0]).to.have.key('script');
expect(result.bool.should[0]).toHaveProperty('script');
});
it('should support date fields without a dateFormat provided', function () {
test('should support date fields without a dateFormat provided', () => {
const expected = {
bool: {
should: [
@ -144,20 +151,23 @@ describe('kuery functions', function () {
'@timestamp': {
gt: '2018-01-03T19:04:17',
lt: '2018-04-03T19:04:17',
}
}
}
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', '@timestamp', { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17' });
const node = nodeTypes.function.buildNode('range', '@timestamp', {
gt: '2018-01-03T19:04:17',
lt: '2018-04-03T19:04:17',
});
const result = range.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should support date fields with a dateFormat provided', function () {
test('should support date fields with a dateFormat provided', () => {
const config = { dateFormatTZ: 'America/Phoenix' };
const expected = {
bool: {
@ -168,20 +178,23 @@ describe('kuery functions', function () {
gt: '2018-01-03T19:04:17',
lt: '2018-04-03T19:04:17',
time_zone: 'America/Phoenix',
}
}
}
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', '@timestamp', { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17' });
const node = nodeTypes.function.buildNode('range', '@timestamp', {
gt: '2018-01-03T19:04:17',
lt: '2018-04-03T19:04:17',
});
const result = range.toElasticsearchQuery(node, indexPattern, config);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should use a provided nested context to create a full field name', function () {
test('should use a provided nested context to create a full field name', () => {
const expected = {
bool: {
should: [
@ -189,15 +202,14 @@ describe('kuery functions', function () {
range: {
'nestedField.bytes': {
gt: 1000,
lt: 8000
}
}
}
lt: 8000,
},
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
const result = range.toElasticsearchQuery(
node,
@ -205,10 +217,11 @@ describe('kuery functions', function () {
{},
{ nested: { path: 'nestedField' } }
);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
it('should automatically add a nested query when a wildcard field name covers a nested field', function () {
test('should automatically add a nested query when a wildcard field name covers a nested field', () => {
const expected = {
bool: {
should: [
@ -219,21 +232,24 @@ describe('kuery functions', function () {
range: {
'nestedField.nestedChild.doublyNestedChild': {
gt: 1000,
lt: 8000
}
}
lt: 8000,
},
},
},
score_mode: 'none'
}
}
score_mode: 'none',
},
},
],
minimum_should_match: 1
}
minimum_should_match: 1,
},
};
const node = nodeTypes.function.buildNode('range', '*doublyNested*', { gt: 1000, lt: 8000 });
const node = nodeTypes.function.buildNode('range', '*doublyNested*', {
gt: 1000,
lt: 8000,
});
const result = range.toElasticsearchQuery(node, indexPattern);
expect(result).to.eql(expected);
expect(result).toEqual(expected);
});
});
});

View file

@ -17,39 +17,41 @@
* under the License.
*/
import { getFields } from '../../utils/get_fields';
import expect from '@kbn/expect';
import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json';
import { fields } from '../../../../index_patterns/mocks';
import { nodeTypes } from '../../..';
import { nodeTypes } from '../../index';
import { IIndexPattern, IFieldType } from '../../../../index_patterns';
let indexPattern;
describe('getFields', function () {
// @ts-ignore
import { getFields } from './get_fields';
describe('getFields', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('field names without a wildcard', function () {
it('should return an empty array if the field does not exist in the index pattern', function () {
describe('field names without a wildcard', () => {
test('should return an empty array if the field does not exist in the index pattern', () => {
const fieldNameNode = nodeTypes.literal.buildNode('nonExistentField');
const expected = [];
const actual = getFields(fieldNameNode, indexPattern);
expect(actual).to.eql(expected);
expect(actual).toEqual([]);
});
it('should return the single matching field in an array', function () {
test('should return the single matching field in an array', () => {
const fieldNameNode = nodeTypes.literal.buildNode('extension');
const results = getFields(fieldNameNode, indexPattern);
expect(results).to.be.an('array');
expect(results).to.have.length(1);
expect(results[0].name).to.be('extension');
expect(results).toHaveLength(1);
expect(Array.isArray(results)).toBeTruthy();
expect(results[0].name).toBe('extension');
});
it('should not match a wildcard in a literal node', function () {
test('should not match a wildcard in a literal node', () => {
const indexPatternWithWildField = {
title: 'wildIndex',
fields: [
@ -61,37 +63,32 @@ describe('getFields', function () {
const fieldNameNode = nodeTypes.literal.buildNode('foo*');
const results = getFields(fieldNameNode, indexPatternWithWildField);
expect(results).to.be.an('array');
expect(results).to.have.length(1);
expect(results[0].name).to.be('foo*');
// ensure the wildcard is not actually being parsed
const expected = [];
expect(results).toHaveLength(1);
expect(Array.isArray(results)).toBeTruthy();
expect(results[0].name).toBe('foo*');
const actual = getFields(nodeTypes.literal.buildNode('fo*'), indexPatternWithWildField);
expect(actual).to.eql(expected);
expect(actual).toEqual([]);
});
});
describe('field name patterns with a wildcard', function () {
it('should return an empty array if it does not match any fields in the index pattern', function () {
describe('field name patterns with a wildcard', () => {
test('should return an empty array if test does not match any fields in the index pattern', () => {
const fieldNameNode = nodeTypes.wildcard.buildNode('nonExistent*');
const expected = [];
const actual = getFields(fieldNameNode, indexPattern);
expect(actual).to.eql(expected);
expect(actual).toEqual([]);
});
it('should return all fields that match the pattern in an array', function () {
test('should return all fields that match the pattern in an array', () => {
const fieldNameNode = nodeTypes.wildcard.buildNode('machine*');
const results = getFields(fieldNameNode, indexPattern);
expect(results).to.be.an('array');
expect(results).to.have.length(2);
expect(results.find((field) => {
return field.name === 'machine.os';
})).to.be.ok();
expect(results.find((field) => {
return field.name === 'machine.os.raw';
})).to.be.ok();
expect(Array.isArray(results)).toBeTruthy();
expect(results).toHaveLength(2);
expect(results.find((field: IFieldType) => field.name === 'machine.os')).toBeDefined();
expect(results.find((field: IFieldType) => field.name === 'machine.os.raw')).toBeDefined();
});
});
});

View file

@ -0,0 +1,87 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { nodeTypes } from '../../node_types';
import { fields } from '../../../../index_patterns/mocks';
import { IIndexPattern } from '../../../../index_patterns';
// @ts-ignore
import { getFullFieldNameNode } from './get_full_field_name_node';
describe('getFullFieldNameNode', function() {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
test('should return unchanged name node if no nested path is passed in', () => {
const nameNode = nodeTypes.literal.buildNode('notNested');
const result = getFullFieldNameNode(nameNode, indexPattern);
expect(result).toEqual(nameNode);
});
test('should add the nested path if test is valid according to the index pattern', () => {
const nameNode = nodeTypes.literal.buildNode('child');
const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField');
expect(result).toEqual(nodeTypes.literal.buildNode('nestedField.child'));
});
test('should throw an error if a path is provided for a non-nested field', () => {
const nameNode = nodeTypes.literal.buildNode('os');
expect(() => getFullFieldNameNode(nameNode, indexPattern, 'machine')).toThrowError(
/machine.os is not a nested field but is in nested group "machine" in the KQL expression/
);
});
test('should throw an error if a nested field is not passed with a path', () => {
const nameNode = nodeTypes.literal.buildNode('nestedField.child');
expect(() => getFullFieldNameNode(nameNode, indexPattern)).toThrowError(
/nestedField.child is a nested field, but is not in a nested group in the KQL expression./
);
});
test('should throw an error if a nested field is passed with the wrong path', () => {
const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild');
expect(() => getFullFieldNameNode(nameNode, indexPattern, 'nestedField')).toThrowError(
/Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/
);
});
test('should skip error checking for wildcard names', () => {
const nameNode = nodeTypes.wildcard.buildNode('nested*');
const result = getFullFieldNameNode(nameNode, indexPattern);
expect(result).toEqual(nameNode);
});
test('should skip error checking if no index pattern is passed in', () => {
const nameNode = nodeTypes.literal.buildNode('os');
expect(() => getFullFieldNameNode(nameNode, null, 'machine')).not.toThrowError();
const result = getFullFieldNameNode(nameNode, null, 'machine');
expect(result).toEqual(nodeTypes.literal.buildNode('machine.os'));
});
});

View file

@ -17,6 +17,8 @@
* under the License.
*/
export * from './ast';
export { KQLSyntaxError } from './kuery_syntax_error';
export { nodeTypes } from './node_types';
export * from './errors';
export * from './ast';
export * from './types';

View file

@ -17,89 +17,92 @@
* under the License.
*/
import { fromKueryExpression } from '../ast';
import { fromKueryExpression } from './ast';
describe('kql syntax errors', () => {
it('should throw an error for a field query missing a value', () => {
expect(() => {
fromKueryExpression('response:');
}).toThrow('Expected "(", "{", value, whitespace but end of input found.\n' +
'response:\n' +
'---------^');
}).toThrow(
'Expected "(", "{", value, whitespace but end of input found.\n' +
'response:\n' +
'---------^'
);
});
it('should throw an error for an OR query missing a right side sub-query', () => {
expect(() => {
fromKueryExpression('response:200 or ');
}).toThrow('Expected "(", NOT, field name, value but end of input found.\n' +
'response:200 or \n' +
'----------------^');
}).toThrow(
'Expected "(", NOT, field name, value but end of input found.\n' +
'response:200 or \n' +
'----------------^'
);
});
it('should throw an error for an OR list of values missing a right side sub-query', () => {
expect(() => {
fromKueryExpression('response:(200 or )');
}).toThrow('Expected "(", NOT, value but ")" found.\n' +
'response:(200 or )\n' +
'-----------------^');
}).toThrow(
'Expected "(", NOT, value but ")" found.\n' + 'response:(200 or )\n' + '-----------------^'
);
});
it('should throw an error for a NOT query missing a sub-query', () => {
expect(() => {
fromKueryExpression('response:200 and not ');
}).toThrow('Expected "(", field name, value but end of input found.\n' +
'response:200 and not \n' +
'---------------------^');
}).toThrow(
'Expected "(", field name, value but end of input found.\n' +
'response:200 and not \n' +
'---------------------^'
);
});
it('should throw an error for a NOT list missing a sub-query', () => {
expect(() => {
fromKueryExpression('response:(200 and not )');
}).toThrow('Expected "(", value but ")" found.\n' +
'response:(200 and not )\n' +
'----------------------^');
}).toThrow(
'Expected "(", value but ")" found.\n' +
'response:(200 and not )\n' +
'----------------------^'
);
});
it('should throw an error for unbalanced quotes', () => {
expect(() => {
fromKueryExpression('foo:"ba ');
}).toThrow('Expected "(", "{", value, whitespace but """ found.\n' +
'foo:"ba \n' +
'----^');
}).toThrow('Expected "(", "{", value, whitespace but """ found.\n' + 'foo:"ba \n' + '----^');
});
it('should throw an error for unescaped quotes in a quoted string', () => {
expect(() => {
fromKueryExpression('foo:"ba "r"');
}).toThrow('Expected AND, OR, end of input, whitespace but "r" found.\n' +
'foo:"ba "r"\n' +
'---------^');
}).toThrow(
'Expected AND, OR, end of input, whitespace but "r" found.\n' + 'foo:"ba "r"\n' + '---------^'
);
});
it('should throw an error for unescaped special characters in literals', () => {
expect(() => {
fromKueryExpression('foo:ba:r');
}).toThrow('Expected AND, OR, end of input, whitespace but ":" found.\n' +
'foo:ba:r\n' +
'------^');
}).toThrow(
'Expected AND, OR, end of input, whitespace but ":" found.\n' + 'foo:ba:r\n' + '------^'
);
});
it('should throw an error for range queries missing a value', () => {
expect(() => {
fromKueryExpression('foo > ');
}).toThrow('Expected literal, whitespace but end of input found.\n' +
'foo > \n' +
'------^');
}).toThrow('Expected literal, whitespace but end of input found.\n' + 'foo > \n' + '------^');
});
it('should throw an error for range queries missing a field', () => {
expect(() => {
fromKueryExpression('< 1000');
}).toThrow('Expected "(", NOT, end of input, field name, value, whitespace but "<" found.\n' +
'< 1000\n' +
'^');
}).toThrow(
'Expected "(", NOT, end of input, field name, value, whitespace but "<" found.\n' +
'< 1000\n' +
'^'
);
});
});

View file

@ -20,35 +20,46 @@
import { repeat } from 'lodash';
import { i18n } from '@kbn/i18n';
const endOfInputText = i18n.translate('kbnESQuery.kql.errors.endOfInputText', {
const endOfInputText = i18n.translate('data.common.esQuery.kql.errors.endOfInputText', {
defaultMessage: 'end of input',
});
const grammarRuleTranslations: Record<string, string> = {
fieldName: i18n.translate('data.common.esQuery.kql.errors.fieldNameText', {
defaultMessage: 'field name',
}),
value: i18n.translate('data.common.esQuery.kql.errors.valueText', {
defaultMessage: 'value',
}),
literal: i18n.translate('data.common.esQuery.kql.errors.literalText', {
defaultMessage: 'literal',
}),
whitespace: i18n.translate('data.common.esQuery.kql.errors.whitespaceText', {
defaultMessage: 'whitespace',
}),
};
interface KQLSyntaxErrorData extends Error {
found: string;
expected: KQLSyntaxErrorExpected[];
location: any;
}
interface KQLSyntaxErrorExpected {
description: string;
}
export class KQLSyntaxError extends Error {
shortMessage: string;
constructor(error, expression) {
const grammarRuleTranslations = {
fieldName: i18n.translate('kbnESQuery.kql.errors.fieldNameText', {
defaultMessage: 'field name',
}),
value: i18n.translate('kbnESQuery.kql.errors.valueText', {
defaultMessage: 'value',
}),
literal: i18n.translate('kbnESQuery.kql.errors.literalText', {
defaultMessage: 'literal',
}),
whitespace: i18n.translate('kbnESQuery.kql.errors.whitespaceText', {
defaultMessage: 'whitespace',
}),
};
const translatedExpectations = error.expected.map((expected) => {
constructor(error: KQLSyntaxErrorData, expression: any) {
const translatedExpectations = error.expected.map(expected => {
return grammarRuleTranslations[expected.description] || expected.description;
});
const translatedExpectationText = translatedExpectations.join(', ');
const message = i18n.translate('kbnESQuery.kql.errors.syntaxError', {
const message = i18n.translate('data.common.esQuery.kql.errors.syntaxError', {
defaultMessage: 'Expected {expectedList} but {foundInput} found.',
values: {
expectedList: translatedExpectationText,
@ -56,11 +67,9 @@ export class KQLSyntaxError extends Error {
},
});
const fullMessage = [
message,
expression,
repeat('-', error.location.start.offset) + '^',
].join('\n');
const fullMessage = [message, expression, repeat('-', error.location.start.offset) + '^'].join(
'\n'
);
super(fullMessage);
this.name = 'KQLSyntaxError';

View file

@ -0,0 +1,75 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { fields } from '../../../index_patterns/mocks';
import { nodeTypes } from './index';
import { IIndexPattern } from '../../../index_patterns';
// @ts-ignore
import { buildNode, buildNodeWithArgumentNodes, toElasticsearchQuery } from './function';
// @ts-ignore
import { toElasticsearchQuery as isFunctionToElasticsearchQuery } from '../functions/is';
describe('kuery node types', () => {
describe('function', () => {
let indexPattern: IIndexPattern;
beforeEach(() => {
indexPattern = ({
fields,
} as unknown) as IIndexPattern;
});
describe('buildNode', () => {
test('should return a node representing the given kuery function', () => {
const result = buildNode('is', 'extension', 'jpg');
expect(result).toHaveProperty('type', 'function');
expect(result).toHaveProperty('function', 'is');
expect(result).toHaveProperty('arguments');
});
});
describe('buildNodeWithArgumentNodes', () => {
test('should return a function node with the given argument list untouched', () => {
const fieldNameLiteral = nodeTypes.literal.buildNode('extension');
const valueLiteral = nodeTypes.literal.buildNode('jpg');
const argumentNodes = [fieldNameLiteral, valueLiteral];
const result = buildNodeWithArgumentNodes('is', argumentNodes);
expect(result).toHaveProperty('type', 'function');
expect(result).toHaveProperty('function', 'is');
expect(result).toHaveProperty('arguments');
expect(result.arguments).toBe(argumentNodes);
expect(result.arguments).toEqual(argumentNodes);
});
});
describe('toElasticsearchQuery', () => {
test("should return the given function type's ES query representation", () => {
const node = buildNode('is', 'extension', 'jpg');
const expected = isFunctionToElasticsearchQuery(node, indexPattern);
const result = toElasticsearchQuery(node, indexPattern);
expect(expected).toEqual(result);
});
});
});
});

View file

@ -21,7 +21,8 @@
* WARNING: these typings are incomplete
*/
import { JsonObject, JsonValue } from '..';
import { IIndexPattern } from '../../../index_patterns';
import { KueryNode, JsonValue } from '..';
type FunctionName =
| 'is'
@ -34,6 +35,17 @@ type FunctionName =
| 'geoPolygon'
| 'nested';
interface FunctionType {
buildNode: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode;
buildNodeWithArgumentNodes: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode;
toElasticsearchQuery: (
node: any,
indexPattern?: IIndexPattern,
config?: Record<string, any>,
context?: Record<string, any>
) => JsonValue;
}
interface FunctionTypeBuildNode {
type: 'function';
function: FunctionName;
@ -41,32 +53,40 @@ interface FunctionTypeBuildNode {
arguments: any[];
}
interface FunctionType {
buildNode: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode;
buildNodeWithArgumentNodes: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode;
toElasticsearchQuery: (node: any, indexPattern: any, config: JsonObject) => JsonValue;
}
interface LiteralType {
buildNode: (
value: null | boolean | number | string
) => { type: 'literal'; value: null | boolean | number | string };
buildNode: (value: null | boolean | number | string) => LiteralTypeBuildNode;
toElasticsearchQuery: (node: any) => null | boolean | number | string;
}
interface LiteralTypeBuildNode {
type: 'literal';
value: null | boolean | number | string;
}
interface NamedArgType {
buildNode: (name: string, value: any) => { type: 'namedArg'; name: string; value: any };
buildNode: (name: string, value: any) => NamedArgTypeBuildNode;
toElasticsearchQuery: (node: any) => string;
}
interface NamedArgTypeBuildNode {
type: 'namedArg';
name: string;
value: any;
}
interface WildcardType {
buildNode: (value: string) => { type: 'wildcard'; value: string };
buildNode: (value: string) => WildcardTypeBuildNode;
test: (node: any, string: string) => boolean;
toElasticsearchQuery: (node: any) => string;
toQueryStringQuery: (node: any) => string;
hasLeadingWildcard: (node: any) => boolean;
}
interface WildcardTypeBuildNode {
type: 'wildcard';
value: string;
}
interface NodeTypes {
function: FunctionType;
literal: LiteralType;

View file

@ -17,34 +17,27 @@
* under the License.
*/
import expect from '@kbn/expect';
import * as literal from '../literal';
// @ts-ignore
import { buildNode, toElasticsearchQuery } from './literal';
describe('kuery node types', function () {
describe('kuery node types', () => {
describe('literal', () => {
describe('buildNode', () => {
test('should return a node representing the given value', () => {
const result = buildNode('foo');
describe('literal', function () {
describe('buildNode', function () {
it('should return a node representing the given value', function () {
const result = literal.buildNode('foo');
expect(result).to.have.property('type', 'literal');
expect(result).to.have.property('value', 'foo');
expect(result).toHaveProperty('type', 'literal');
expect(result).toHaveProperty('value', 'foo');
});
});
describe('toElasticsearchQuery', function () {
describe('toElasticsearchQuery', () => {
test('should return the literal value represented by the given node', () => {
const node = buildNode('foo');
const result = toElasticsearchQuery(node);
it('should return the literal value represented by the given node', function () {
const node = literal.buildNode('foo');
const result = literal.toElasticsearchQuery(node);
expect(result).to.be('foo');
expect(result).toBe('foo');
});
});
});
});

View file

@ -0,0 +1,57 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { nodeTypes } from './index';
// @ts-ignore
import { buildNode, toElasticsearchQuery } from './named_arg';
describe('kuery node types', () => {
describe('named arg', () => {
describe('buildNode', () => {
test('should return a node representing a named argument with the given value', () => {
const result = buildNode('fieldName', 'foo');
expect(result).toHaveProperty('type', 'namedArg');
expect(result).toHaveProperty('name', 'fieldName');
expect(result).toHaveProperty('value');
const literalValue = result.value;
expect(literalValue).toHaveProperty('type', 'literal');
expect(literalValue).toHaveProperty('value', 'foo');
});
test('should support literal nodes as values', () => {
const value = nodeTypes.literal.buildNode('foo');
const result = buildNode('fieldName', value);
expect(result.value).toBe(value);
expect(result.value).toEqual(value);
});
});
describe('toElasticsearchQuery', () => {
test('should return the argument value represented by the given node', () => {
const node = buildNode('fieldName', 'foo');
const result = toElasticsearchQuery(node);
expect(result).toBe('foo');
});
});
});
});

View file

@ -0,0 +1,110 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
buildNode,
wildcardSymbol,
hasLeadingWildcard,
toElasticsearchQuery,
test as testNode,
toQueryStringQuery,
// @ts-ignore
} from './wildcard';
describe('kuery node types', () => {
describe('wildcard', () => {
describe('buildNode', () => {
test('should accept a string argument representing a wildcard string', () => {
const wildcardValue = `foo${wildcardSymbol}bar`;
const result = buildNode(wildcardValue);
expect(result).toHaveProperty('type', 'wildcard');
expect(result).toHaveProperty('value', wildcardValue);
});
test('should accept and parse a wildcard string', () => {
const result = buildNode('foo*bar');
expect(result).toHaveProperty('type', 'wildcard');
expect(result.value).toBe(`foo${wildcardSymbol}bar`);
});
});
describe('toElasticsearchQuery', () => {
test('should return the string representation of the wildcard literal', () => {
const node = buildNode('foo*bar');
const result = toElasticsearchQuery(node);
expect(result).toBe('foo*bar');
});
});
describe('toQueryStringQuery', () => {
test('should return the string representation of the wildcard literal', () => {
const node = buildNode('foo*bar');
const result = toQueryStringQuery(node);
expect(result).toBe('foo*bar');
});
test('should escape query_string query special characters other than wildcard', () => {
const node = buildNode('+foo*bar');
const result = toQueryStringQuery(node);
expect(result).toBe('\\+foo*bar');
});
});
describe('test', () => {
test('should return a boolean indicating whether the string matches the given wildcard node', () => {
const node = buildNode('foo*bar');
expect(testNode(node, 'foobar')).toBe(true);
expect(testNode(node, 'foobazbar')).toBe(true);
expect(testNode(node, 'foobar')).toBe(true);
expect(testNode(node, 'fooqux')).toBe(false);
expect(testNode(node, 'bazbar')).toBe(false);
});
test('should return a true even when the string has newlines or tabs', () => {
const node = buildNode('foo*bar');
expect(testNode(node, 'foo\nbar')).toBe(true);
expect(testNode(node, 'foo\tbar')).toBe(true);
});
});
describe('hasLeadingWildcard', () => {
test('should determine whether a wildcard node contains a leading wildcard', () => {
const node = buildNode('foo*bar');
expect(hasLeadingWildcard(node)).toBe(false);
const leadingWildcardNode = buildNode('*foobar');
expect(hasLeadingWildcard(leadingWildcardNode)).toBe(true);
});
// Lone wildcards become exists queries, so we aren't worried about their performance
test('should not consider a lone wildcard to be a leading wildcard', () => {
const leadingWildcardNode = buildNode('*');
expect(hasLeadingWildcard(leadingWildcardNode)).toBe(false);
});
});
});
});

View file

@ -17,14 +17,29 @@
* under the License.
*/
export * from './ast';
import { NodeTypes } from './node_types';
export interface KueryNode {
type: keyof NodeTypes;
[key: string]: any;
}
export type DslQuery = any;
export interface KueryParseOptions {
helpers: {
[key: string]: any;
};
startRule: string;
allowLeadingWildcards: boolean;
errorOnLuceneSyntax: boolean;
}
export { nodeTypes } from './node_types';
export type JsonArray = JsonValue[];
export type JsonValue = null | boolean | number | string | JsonObject | JsonArray;
export interface JsonObject {
[key: string]: JsonValue;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface JsonArray extends Array<JsonValue> {}

View file

@ -17,10 +17,10 @@
* under the License.
*/
import { FieldFormat } from '../field_format';
import { FieldFormat, IFieldFormatType } from '../field_format';
import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types';
export const createCustomFieldFormat = (convert: TextContextTypeConvert) =>
export const createCustomFieldFormat = (convert: TextContextTypeConvert): IFieldFormatType =>
class CustomFieldFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.CUSTOM;

View file

@ -73,7 +73,7 @@ export abstract class FieldFormat {
*/
public type: any = this.constructor;
private readonly _params: any;
protected readonly _params: any;
protected getConfig: Function | undefined;
constructor(_params: any = {}, getConfig?: Function) {

View file

@ -19,8 +19,8 @@
module.exports = {
kuery: {
src: 'packages/kbn-es-query/src/kuery/ast/kuery.peg',
dest: 'packages/kbn-es-query/src/kuery/ast/kuery.js',
src: 'src/plugins/data/common/es_query/kuery/ast/kuery.peg',
dest: 'src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js',
options: {
allowedStartRules: ['start', 'Literal']
}

View file

@ -7,8 +7,6 @@
import React, { useState } from 'react';
import { uniqueId, startsWith } from 'lodash';
import styled from 'styled-components';
import { StaticIndexPattern } from 'ui/index_patterns';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { fromQuery, toQuery } from '../Links/url_helpers';
// @ts-ignore
@ -17,12 +15,14 @@ import { getBoolFilter } from './get_bool_filter';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { history } from '../../../utils/history';
import {
AutocompleteSuggestion,
AutocompleteProvider
} from '../../../../../../../../src/plugins/data/public';
import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern';
import { usePlugins } from '../../../new-platform/plugin';
import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern';
import {
AutocompleteProvider,
AutocompleteSuggestion,
esKuery,
IIndexPattern
} from '../../../../../../../../src/plugins/data/public';
const Container = styled.div`
margin-bottom: 10px;
@ -33,18 +33,15 @@ interface State {
isLoadingSuggestions: boolean;
}
function convertKueryToEsQuery(
kuery: string,
indexPattern: StaticIndexPattern
) {
const ast = fromKueryExpression(kuery);
return toElasticsearchQuery(ast, indexPattern);
function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) {
const ast = esKuery.fromKueryExpression(kuery);
return esKuery.toElasticsearchQuery(ast, indexPattern);
}
function getSuggestions(
query: string,
selectionStart: number,
indexPattern: StaticIndexPattern,
indexPattern: IIndexPattern,
boolFilter: unknown,
autocompleteProvider?: AutocompleteProvider
) {

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query';
import { ESFilter } from '../../../../typings/elasticsearch';
import { UIFilters } from '../../../../typings/ui-filters';
import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es';
@ -12,10 +11,13 @@ import {
localUIFilters,
localUIFilterNames
} from '../../ui_filters/local_ui_filters/config';
import { StaticIndexPattern } from '../../../../../../../../src/legacy/core_plugins/data/public';
import {
esKuery,
IIndexPattern
} from '../../../../../../../../src/plugins/data/server';
export function getUiFiltersES(
indexPattern: StaticIndexPattern | undefined,
indexPattern: IIndexPattern | undefined,
uiFilters: UIFilters
) {
const { kuery, environment, ...localFilterValues } = uiFilters;
@ -43,13 +45,13 @@ export function getUiFiltersES(
}
function getKueryUiFilterES(
indexPattern: StaticIndexPattern | undefined,
indexPattern: IIndexPattern | undefined,
kuery?: string
) {
if (!kuery || !indexPattern) {
return;
}
const ast = fromKueryExpression(kuery);
return toElasticsearchQuery(ast, indexPattern) as ESFilter;
const ast = esKuery.fromKueryExpression(kuery);
return esKuery.toElasticsearchQuery(ast, indexPattern) as ESFilter;
}

View file

@ -4,11 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { isEmpty } from 'lodash';
import { npStart } from 'ui/new_platform';
import { ElasticsearchAdapter } from './adapter_types';
import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public';
import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public';
import { setup as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy';
const getAutocompleteProvider = (language: string) =>
@ -20,7 +19,7 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter {
public isKueryValid(kuery: string): boolean {
try {
fromKueryExpression(kuery);
esKuery.fromKueryExpression(kuery);
} catch (err) {
return false;
}
@ -31,9 +30,9 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter {
if (!this.isKueryValid(kuery)) {
return '';
}
const ast = fromKueryExpression(kuery);
const ast = esKuery.fromKueryExpression(kuery);
const indexPattern = await this.getIndexPattern();
return JSON.stringify(toElasticsearchQuery(ast, indexPattern));
return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern));
}
public async getSuggestions(
kuery: string,

Some files were not shown because too many files have changed in this diff Show more