mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[kql] Remove named args and unused geo functions (#118973)
* [kql] Remove named args and unused geo functions * Fix tests * Re-format grammar output
This commit is contained in:
parent
2c440da1d6
commit
35739880a6
14 changed files with 48 additions and 588 deletions
|
@ -12,7 +12,6 @@
|
|||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||
const buildNamedArgNode = nodeTypes.namedArg.buildNode;
|
||||
const { wildcardSymbol } = nodeTypes.wildcard;
|
||||
}
|
||||
|
||||
|
@ -100,8 +99,7 @@ FieldRangeExpression
|
|||
suggestionTypes: ['conjunction']
|
||||
};
|
||||
}
|
||||
const range = buildNamedArgNode(operator, value);
|
||||
return buildFunctionNode('range', [field, range]);
|
||||
return buildFunctionNode('range', [field, operator, value]);
|
||||
}
|
||||
|
||||
FieldValueExpression
|
||||
|
|
|
@ -174,12 +174,8 @@ describe('kuery AST API', () => {
|
|||
|
||||
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',
|
||||
}),
|
||||
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);
|
||||
|
@ -187,12 +183,8 @@ describe('kuery AST API', () => {
|
|||
|
||||
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',
|
||||
}),
|
||||
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);
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../..';
|
||||
|
||||
import * as geoBoundingBox from './geo_bounding_box';
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
const params = {
|
||||
bottomRight: {
|
||||
lat: 50.73,
|
||||
lon: -135.35,
|
||||
},
|
||||
topLeft: {
|
||||
lat: 73.12,
|
||||
lon: -174.37,
|
||||
},
|
||||
};
|
||||
|
||||
describe('kuery functions', () => {
|
||||
describe('geoBoundingBox', () => {
|
||||
let indexPattern: DataViewBase;
|
||||
|
||||
beforeEach(() => {
|
||||
indexPattern = {
|
||||
fields,
|
||||
title: 'dataView',
|
||||
};
|
||||
});
|
||||
|
||||
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 as JsonObject).geo).toHaveProperty(
|
||||
'top_left',
|
||||
'73.12, -174.37'
|
||||
);
|
||||
expect((result.geo_bounding_box as JsonObject).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 as JsonObject).geo).toHaveProperty(
|
||||
'top_left',
|
||||
'73.12, -174.37'
|
||||
);
|
||||
expect((result.geo_bounding_box as JsonObject).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 as JsonObject)['nestedField.geo']).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import * as ast from '../ast';
|
||||
import { IndexPatternBase, KueryNode, KueryQueryOptions, LatLon } from '../..';
|
||||
|
||||
export function buildNodeParams(fieldName: string, params: any) {
|
||||
params = _.pick(params, 'topLeft', 'bottomRight');
|
||||
const fieldNameArg = nodeTypes.literal.buildNode(fieldName);
|
||||
const args = _.map(params, (value: LatLon, key: string) => {
|
||||
const latLon = `${value.lat}, ${value.lon}`;
|
||||
return nodeTypes.namedArg.buildNode(key, latLon);
|
||||
});
|
||||
|
||||
return {
|
||||
arguments: [fieldNameArg, ...args],
|
||||
};
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
indexPattern?: IndexPatternBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: Record<string, any> = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
const [fieldNameArg, ...args] = node.arguments;
|
||||
const fullFieldNameArg = {
|
||||
...fieldNameArg,
|
||||
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
|
||||
};
|
||||
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
|
||||
const fieldList = indexPattern?.fields ?? [];
|
||||
const field = fieldList.find((fld) => fld.name === fieldName);
|
||||
|
||||
const queryParams = args.reduce((acc: any, arg: any) => {
|
||||
const snakeArgName = _.snakeCase(arg.name);
|
||||
return {
|
||||
...acc,
|
||||
[snakeArgName]: ast.toElasticsearchQuery(arg),
|
||||
};
|
||||
}, {});
|
||||
|
||||
if (field?.scripted) {
|
||||
throw new Error(`Geo bounding box query does not support scripted fields`);
|
||||
}
|
||||
|
||||
return {
|
||||
geo_bounding_box: {
|
||||
[fieldName]: queryParams,
|
||||
ignore_unmapped: true,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { nodeTypes } from '../node_types';
|
||||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../..';
|
||||
|
||||
import * as geoPolygon from './geo_polygon';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
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: DataViewBase;
|
||||
|
||||
beforeEach(() => {
|
||||
indexPattern = {
|
||||
fields,
|
||||
title: 'dataView',
|
||||
};
|
||||
});
|
||||
|
||||
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 as any).geo).toHaveProperty('points');
|
||||
|
||||
(result.geo_polygon as any).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 as any).geo).toHaveProperty('points');
|
||||
|
||||
(result.geo_polygon as any).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 as any).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 as any)['nestedField.geo']).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import * as ast from '../ast';
|
||||
import { IndexPatternBase, KueryNode, KueryQueryOptions, LatLon } from '../..';
|
||||
import { LiteralTypeBuildNode } from '../node_types/types';
|
||||
|
||||
export function buildNodeParams(fieldName: string, points: LatLon[]) {
|
||||
const fieldNameArg = nodeTypes.literal.buildNode(fieldName);
|
||||
const args = points.map((point) => {
|
||||
const latLon = `${point.lat}, ${point.lon}`;
|
||||
return nodeTypes.literal.buildNode(latLon);
|
||||
});
|
||||
|
||||
return {
|
||||
arguments: [fieldNameArg, ...args],
|
||||
};
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
indexPattern?: IndexPatternBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: Record<string, any> = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
const [fieldNameArg, ...points] = node.arguments;
|
||||
const fullFieldNameArg = {
|
||||
...fieldNameArg,
|
||||
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
|
||||
};
|
||||
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
|
||||
const fieldList = indexPattern?.fields ?? [];
|
||||
const field = fieldList.find((fld) => fld.name === fieldName);
|
||||
const queryParams = {
|
||||
points: points.map((point: LiteralTypeBuildNode) => {
|
||||
return ast.toElasticsearchQuery(point, indexPattern, config, context);
|
||||
}),
|
||||
};
|
||||
|
||||
if (field?.scripted) {
|
||||
throw new Error(`Geo polygon query does not support scripted fields`);
|
||||
}
|
||||
|
||||
return {
|
||||
geo_polygon: {
|
||||
[fieldName]: queryParams,
|
||||
ignore_unmapped: true,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -12,8 +12,6 @@ import * as or from './or';
|
|||
import * as not from './not';
|
||||
import * as range from './range';
|
||||
import * as exists from './exists';
|
||||
import * as geoBoundingBox from './geo_bounding_box';
|
||||
import * as geoPolygon from './geo_polygon';
|
||||
import * as nested from './nested';
|
||||
|
||||
export const functions = {
|
||||
|
@ -23,7 +21,5 @@ export const functions = {
|
|||
not,
|
||||
range,
|
||||
exists,
|
||||
geoBoundingBox,
|
||||
geoPolygon,
|
||||
nested,
|
||||
};
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../..';
|
||||
import { RangeFilterParams } from '../../filters';
|
||||
|
||||
import * as range from './range';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
describe('kuery functions', () => {
|
||||
|
@ -29,7 +28,7 @@ describe('kuery functions', () => {
|
|||
|
||||
describe('buildNodeParams', () => {
|
||||
test('arguments should contain the provided fieldName as a literal', () => {
|
||||
const result = range.buildNodeParams('bytes', { gt: 1000, lt: 8000 });
|
||||
const result = range.buildNodeParams('bytes', 'gt', 1000);
|
||||
const {
|
||||
arguments: [fieldName],
|
||||
} = result;
|
||||
|
@ -38,22 +37,14 @@ describe('kuery functions', () => {
|
|||
expect(fieldName).toHaveProperty('value', 'bytes');
|
||||
});
|
||||
|
||||
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);
|
||||
test('arguments should contain the provided value as a literal', () => {
|
||||
const result = range.buildNodeParams('bytes', 'gt', 1000);
|
||||
const {
|
||||
arguments: [, ...params],
|
||||
arguments: [, , valueArg],
|
||||
} = result;
|
||||
|
||||
expect(Array.isArray(params)).toBeTruthy();
|
||||
expect(params.length).toBeGreaterThan(1);
|
||||
|
||||
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));
|
||||
});
|
||||
expect(valueArg).toHaveProperty('type', 'literal');
|
||||
expect(valueArg).toHaveProperty('value', 1000);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -66,7 +57,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
bytes: {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -74,7 +64,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -88,7 +78,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
bytes: {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -97,7 +86,7 @@ describe('kuery functions', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const result = range.toElasticsearchQuery(node);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -111,7 +100,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
bytes: {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -120,14 +108,14 @@ describe('kuery functions', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('range', 'byt*', { gt: 1000, lt: 8000 });
|
||||
const node = nodeTypes.function.buildNode('range', 'byt*', 'gt', 1000);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should support scripted fields', () => {
|
||||
const node = nodeTypes.function.buildNode('range', 'script number', { gt: 1000, lt: 8000 });
|
||||
const node = nodeTypes.function.buildNode('range', 'script number', 'gt', 1000);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect((result.bool!.should as estypes.QueryDslQueryContainer[])[0]).toHaveProperty(
|
||||
|
@ -143,7 +131,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
'@timestamp': {
|
||||
gt: '2018-01-03T19:04:17',
|
||||
lt: '2018-04-03T19:04:17',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -151,10 +138,12 @@ describe('kuery functions', () => {
|
|||
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'
|
||||
);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -169,7 +158,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
'@timestamp': {
|
||||
gt: '2018-01-03T19:04:17',
|
||||
lt: '2018-04-03T19:04:17',
|
||||
time_zone: 'America/Phoenix',
|
||||
},
|
||||
},
|
||||
|
@ -178,10 +166,12 @@ describe('kuery functions', () => {
|
|||
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'
|
||||
);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern, config);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -195,7 +185,6 @@ describe('kuery functions', () => {
|
|||
range: {
|
||||
'nestedField.bytes': {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -203,7 +192,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const result = range.toElasticsearchQuery(
|
||||
node,
|
||||
indexPattern,
|
||||
|
@ -224,7 +213,6 @@ describe('kuery functions', () => {
|
|||
query: {
|
||||
range: {
|
||||
'nestedField.nestedChild.doublyNestedChild': {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
},
|
||||
},
|
||||
|
@ -236,10 +224,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', '*doublyNested*', {
|
||||
gt: 1000,
|
||||
lt: 8000,
|
||||
});
|
||||
const node = nodeTypes.function.buildNode('range', '*doublyNested*', 'lt', 8000);
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
|
|
@ -6,30 +6,24 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { pick, map, mapValues } from 'lodash';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import * as ast from '../ast';
|
||||
import { getRangeScript, RangeFilterParams } from '../../filters';
|
||||
import { getFields } from './utils/get_fields';
|
||||
import { getTimeZoneFromSettings, getDataViewFieldSubtypeNested } from '../../utils';
|
||||
import { getDataViewFieldSubtypeNested, getTimeZoneFromSettings } from '../../utils';
|
||||
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
|
||||
import { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';
|
||||
import type { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';
|
||||
|
||||
export function buildNodeParams(fieldName: string, params: RangeFilterParams) {
|
||||
const paramsToMap = pick(params, 'gt', 'lt', 'gte', 'lte', 'format');
|
||||
const fieldNameArg =
|
||||
typeof fieldName === 'string'
|
||||
? ast.fromLiteralExpression(fieldName)
|
||||
: nodeTypes.literal.buildNode(fieldName);
|
||||
|
||||
const args = map(paramsToMap, (value: number | string, key: string) => {
|
||||
return nodeTypes.namedArg.buildNode(key, value);
|
||||
});
|
||||
|
||||
return {
|
||||
arguments: [fieldNameArg, ...args],
|
||||
};
|
||||
export function buildNodeParams(
|
||||
fieldName: string,
|
||||
operator: keyof Pick<RangeFilterParams, 'gt' | 'gte' | 'lt' | 'lte'>,
|
||||
value: number | string
|
||||
) {
|
||||
// Run through the parser instead treating it as a literal because it may contain wildcards
|
||||
const fieldNameArg = ast.fromLiteralExpression(fieldName);
|
||||
const valueArg = nodeTypes.literal.buildNode(value);
|
||||
return { arguments: [fieldNameArg, operator, valueArg] };
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
|
@ -38,17 +32,13 @@ export function toElasticsearchQuery(
|
|||
config: KueryQueryOptions = {},
|
||||
context: Record<string, any> = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
const [fieldNameArg, ...args] = node.arguments;
|
||||
const [fieldNameArg, operatorArg, valueArg] = node.arguments;
|
||||
const fullFieldNameArg = getFullFieldNameNode(
|
||||
fieldNameArg,
|
||||
indexPattern,
|
||||
context?.nested ? context.nested.path : undefined
|
||||
);
|
||||
const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : [];
|
||||
const namedArgs = extractArguments(args);
|
||||
const queryParams = mapValues(namedArgs, (arg: KueryNode) => {
|
||||
return ast.toElasticsearchQuery(arg);
|
||||
});
|
||||
|
||||
// If no fields are found in the index pattern we send through the given field name as-is. We do this to preserve
|
||||
// the behaviour of lucene on dashboards where there are panels based on different index patterns that have different
|
||||
|
@ -81,6 +71,10 @@ export function toElasticsearchQuery(
|
|||
}
|
||||
};
|
||||
|
||||
const queryParams = {
|
||||
[operatorArg]: ast.toElasticsearchQuery(valueArg),
|
||||
};
|
||||
|
||||
if (field.scripted) {
|
||||
return {
|
||||
script: getRangeScript(field, queryParams),
|
||||
|
@ -112,21 +106,3 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
function extractArguments(args: any) {
|
||||
if ((args.gt && args.gte) || (args.lt && args.lte)) {
|
||||
throw new Error('range ends cannot be both inclusive and exclusive');
|
||||
}
|
||||
|
||||
const unnamedArgOrder = ['gte', 'lte', 'format'];
|
||||
|
||||
return args.reduce((acc: any, arg: any, index: number) => {
|
||||
if (arg.type === 'namedArg') {
|
||||
acc[arg.name] = arg.value;
|
||||
} else {
|
||||
acc[unnamedArgOrder[index]] = arg;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
|
|
@ -300,8 +300,7 @@ function peg$parse(input, options) {
|
|||
suggestionTypes: ['conjunction']
|
||||
};
|
||||
}
|
||||
const range = buildNamedArgNode(operator, value);
|
||||
return buildFunctionNode('range', [field, range]);
|
||||
return buildFunctionNode('range', [field, operator, value]);
|
||||
};
|
||||
var peg$f8 = function(field, partial) {
|
||||
if (partial.type === 'cursor') {
|
||||
|
@ -2190,7 +2189,6 @@ function peg$parse(input, options) {
|
|||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||
const buildNamedArgNode = nodeTypes.namedArg.buildNode;
|
||||
const { wildcardSymbol } = nodeTypes.wildcard;
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import * as functionType from './function';
|
||||
import * as literal from './literal';
|
||||
import * as namedArg from './named_arg';
|
||||
import * as wildcard from './wildcard';
|
||||
import { FunctionTypeBuildNode, NodeTypes } from './types';
|
||||
|
||||
|
@ -23,6 +22,5 @@ export const nodeTypes: NodeTypes = {
|
|||
// @ts-ignore
|
||||
function: functionType,
|
||||
literal,
|
||||
namedArg,
|
||||
wildcard,
|
||||
};
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { nodeTypes } from './index';
|
||||
import { buildNode, toElasticsearchQuery } from './named_arg';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { JsonObject } from '@kbn/utility-types';
|
||||
import * as ast from '../ast';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import { NamedArgTypeBuildNode } from './types';
|
||||
|
||||
export function buildNode(name: string, value: any): NamedArgTypeBuildNode {
|
||||
const argumentNode =
|
||||
_.get(value, 'type') === 'literal' ? value : nodeTypes.literal.buildNode(value);
|
||||
return {
|
||||
type: 'namedArg',
|
||||
name,
|
||||
value: argumentNode,
|
||||
};
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(node: any): JsonObject {
|
||||
return ast.toElasticsearchQuery(node.value);
|
||||
}
|
|
@ -14,16 +14,7 @@ import { JsonValue } from '@kbn/utility-types';
|
|||
import { KueryNode, KueryQueryOptions } from '..';
|
||||
import { IndexPatternBase } from '../..';
|
||||
|
||||
export type FunctionName =
|
||||
| 'is'
|
||||
| 'and'
|
||||
| 'or'
|
||||
| 'not'
|
||||
| 'range'
|
||||
| 'exists'
|
||||
| 'geoBoundingBox'
|
||||
| 'geoPolygon'
|
||||
| 'nested';
|
||||
export type FunctionName = 'is' | 'and' | 'or' | 'not' | 'range' | 'exists' | 'nested';
|
||||
|
||||
interface FunctionType {
|
||||
buildNode: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode;
|
||||
|
@ -53,17 +44,6 @@ export interface LiteralTypeBuildNode {
|
|||
value: null | boolean | number | string;
|
||||
}
|
||||
|
||||
interface NamedArgType {
|
||||
buildNode: (name: string, value: any) => NamedArgTypeBuildNode;
|
||||
toElasticsearchQuery: (node: any) => JsonValue;
|
||||
}
|
||||
|
||||
export interface NamedArgTypeBuildNode {
|
||||
type: 'namedArg';
|
||||
name: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface WildcardType {
|
||||
wildcardSymbol: string;
|
||||
buildNode: (value: string) => WildcardTypeBuildNode | KueryNode;
|
||||
|
@ -81,6 +61,5 @@ export interface WildcardTypeBuildNode {
|
|||
export interface NodeTypes {
|
||||
function: FunctionType;
|
||||
literal: LiteralType;
|
||||
namedArg: NamedArgType;
|
||||
wildcard: WildcardType;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue