Skip to content

Commit

Permalink
Typescript es-query functions (elastic#57473) (elastic#58695)
Browse files Browse the repository at this point in the history
* TS Functions

* Rename index.ts

* tsing

* JSON Value

* ts ignore

* TS adjustments

* context?.nested?.path

* Code review

* Import LiteralTypeBuildNode

* Fix jest tests

* revert test

* TS fix IFieldType

* Remvoe unnecessary casting

* range types

Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
Liza Katz and elasticmachine authored Feb 27, 2020
1 parent 6d23892 commit 3e491f8
Show file tree
Hide file tree
Showing 18 changed files with 159 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@
*/

import * as ast from '../ast';
import { IIndexPattern, KueryNode } from '../../..';

export function buildNodeParams(children) {
export function buildNodeParams(children: KueryNode[]) {
return {
arguments: children,
};
}

export function toElasticsearchQuery(node, indexPattern, config, context) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const children = node.arguments || [];

return {
bool: {
filter: children.map(child => {
filter: children.map((child: KueryNode) => {
return ast.toElasticsearchQuery(child, indexPattern, config, context);
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,31 @@

import { get } from 'lodash';
import * as literal from '../node_types/literal';
import { IIndexPattern, KueryNode, IFieldType } from '../../..';

export function buildNodeParams(fieldName) {
export function buildNodeParams(fieldName: string) {
return {
arguments: [literal.buildNode(fieldName)],
};
}

export function toElasticsearchQuery(node, indexPattern = null, config, context = {}) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const {
arguments: [fieldNameArg],
} = node;
const fullFieldNameArg = {
...fieldNameArg,
value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = literal.toElasticsearchQuery(fullFieldNameArg);
const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName);
const field = get(indexPattern, 'fields', []).find((fld: IFieldType) => fld.name === fieldName);

if (field && field.scripted) {
if (field && (field as IFieldType).scripted) {
throw new Error(`Exists query does not support scripted fields`);
}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import _ from 'lodash';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { IIndexPattern, KueryNode, IFieldType, LatLon } from '../../..';

export function buildNodeParams(fieldName, params) {
export function buildNodeParams(fieldName: string, params: any) {
params = _.pick(params, 'topLeft', 'bottomRight');
const fieldNameArg = nodeTypes.literal.buildNode(fieldName);
const args = _.map(params, (value, key) => {
const args = _.map(params, (value: LatLon, key: string) => {
const latLon = `${value.lat}, ${value.lon}`;
return nodeTypes.namedArg.buildNode(key, latLon);
});
Expand All @@ -34,23 +35,30 @@ export function buildNodeParams(fieldName, params) {
};
}

export function toElasticsearchQuery(node, indexPattern, config, context = {}) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const [fieldNameArg, ...args] = node.arguments;
const fullFieldNameArg = {
...fieldNameArg,
value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg);
const field = _.get(indexPattern, 'fields', []).find(field => field.name === fieldName);
const queryParams = args.reduce((acc, arg) => {
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
const fieldList: IFieldType[] = indexPattern?.fields ?? [];
const field = fieldList.find((fld: IFieldType) => fld.name === fieldName);

const queryParams = args.reduce((acc: any, arg: any) => {
const snakeArgName = _.snakeCase(arg.name);
return {
...acc,
[snakeArgName]: ast.toElasticsearchQuery(arg),
};
}, {});

if (field && field.scripted) {
if (field?.scripted) {
throw new Error(`Geo bounding box query does not support scripted fields`);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('kuery functions', () => {
expect(result).toHaveProperty('geo_polygon');
expect(result.geo_polygon.geo).toHaveProperty('points');

result.geo_polygon.geo.points.forEach((point: any, index: number) => {
(result.geo_polygon.geo as any).points.forEach((point: any, index: number) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;

expect(point).toBe(expectedLatLon);
Expand All @@ -105,7 +105,7 @@ describe('kuery functions', () => {
expect(result).toHaveProperty('geo_polygon');
expect(result.geo_polygon.geo).toHaveProperty('points');

result.geo_polygon.geo.points.forEach((point: any, index: number) => {
(result.geo_polygon.geo as any).points.forEach((point: any, index: number) => {
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;

expect(point).toBe(expectedLatLon);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
* under the License.
*/

import { get } from 'lodash';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { IIndexPattern, KueryNode, IFieldType, LatLon } from '../../..';
import { LiteralTypeBuildNode } from '../node_types/types';

export function buildNodeParams(fieldName, points) {
export function buildNodeParams(fieldName: string, points: LatLon[]) {
const fieldNameArg = nodeTypes.literal.buildNode(fieldName);
const args = points.map(point => {
const latLon = `${point.lat}, ${point.lon}`;
Expand All @@ -33,21 +34,27 @@ export function buildNodeParams(fieldName, points) {
};
}

export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const [fieldNameArg, ...points] = node.arguments;
const fullFieldNameArg = {
...fieldNameArg,
value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg);
const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName);
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
const fieldList: IFieldType[] = indexPattern?.fields ?? [];
const field = fieldList.find((fld: IFieldType) => fld.name === fieldName);
const queryParams = {
points: points.map(point => {
points: points.map((point: LiteralTypeBuildNode) => {
return ast.toElasticsearchQuery(point, indexPattern, config, context);
}),
};

if (field && field.scripted) {
if (field?.scripted) {
throw new Error(`Geo polygon query does not support scripted fields`);
}

Expand Down
9 changes: 2 additions & 7 deletions src/plugins/data/common/es_query/kuery/functions/is.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ describe('kuery functions', () => {
});

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],
Expand Down Expand Up @@ -117,7 +112,7 @@ describe('kuery functions', () => {
const result = is.toElasticsearchQuery(node, indexPattern);

expect(result).toHaveProperty('bool');
expect(result.bool.should.length).toBe(indexPattern.fields.length);
expect(result.bool!.should!.length).toBe(indexPattern.fields.length);
});

test('should return an ES exists query when value is "*"', () => {
Expand Down Expand Up @@ -196,7 +191,7 @@ describe('kuery functions', () => {
const node = nodeTypes.function.buildNode('is', 'script string', 'foo');
const result = is.toElasticsearchQuery(node, indexPattern);

expect(result.bool.should[0]).toHaveProperty('script');
expect(result.bool!.should![0]).toHaveProperty('script');
});

test('should support date fields without a dateFormat provided', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
import { IIndexPattern, KueryNode, IFieldType } from '../../..';

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) {
export function buildNodeParams(fieldName: string, value: any, isPhrase: boolean = false) {
if (isUndefined(fieldName)) {
throw new Error('fieldName is a required argument');
}
Expand All @@ -47,14 +48,19 @@ export function buildNodeParams(fieldName, value, isPhrase = false) {
};
}

export function toElasticsearchQuery(node, indexPattern = null, config = {}, context = {}) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const {
arguments: [fieldNameArg, valueArg, isPhraseArg],
} = node;
const fullFieldNameArg = getFullFieldNameNode(
fieldNameArg,
indexPattern,
context.nested ? context.nested.path : undefined
context?.nested ? context.nested.path : undefined
);
const fieldName = ast.toElasticsearchQuery(fullFieldNameArg);
const value = !isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg;
Expand Down Expand Up @@ -85,35 +91,36 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con
// keep things familiar for now.
if (fields && fields.length === 0) {
fields.push({
name: ast.toElasticsearchQuery(fullFieldNameArg),
name: (ast.toElasticsearchQuery(fullFieldNameArg) as unknown) as string,
scripted: false,
type: '',
});
}

const isExistsQuery = valueArg.type === 'wildcard' && value === '*';
const isAllFieldsQuery =
(fullFieldNameArg.type === 'wildcard' && fieldName === '*') ||
(fullFieldNameArg.type === 'wildcard' && ((fieldName as unknown) as string) === '*') ||
(fields && indexPattern && fields.length === indexPattern.fields.length);
const isMatchAllQuery = isExistsQuery && isAllFieldsQuery;

if (isMatchAllQuery) {
return { match_all: {} };
}

const queries = fields.reduce((accumulator, field) => {
const wrapWithNestedQuery = query => {
const queries = fields!.reduce((accumulator: any, field: IFieldType) => {
const wrapWithNestedQuery = (query: any) => {
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
// users handle this themselves so we automatically add nested queries in this scenario.
if (
!(fullFieldNameArg.type === 'wildcard') ||
!get(field, 'subType.nested') ||
context.nested
context?.nested
) {
return query;
} else {
return {
nested: {
path: field.subType.nested.path,
path: field.subType!.nested!.path,
query,
score_mode: 'none',
},
Expand Down Expand Up @@ -158,7 +165,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con
dateFormatTZ can have the value of 'Browser', in which case we guess the timezone using moment.tz.guess.
*/
const timeZoneParam = config.dateFormatTZ
? { time_zone: getTimeZoneFromSettings(config.dateFormatTZ) }
? { time_zone: getTimeZoneFromSettings(config!.dateFormatTZ) }
: {};
return [
...accumulator,
Expand Down Expand Up @@ -187,7 +194,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con

return {
bool: {
should: queries,
should: queries || [],
minimum_should_match: 1,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@

import * as ast from '../ast';
import * as literal from '../node_types/literal';
import { IIndexPattern, KueryNode } from '../../..';

export function buildNodeParams(path, child) {
export function buildNodeParams(path: any, child: any) {
const pathNode =
typeof path === 'string' ? ast.fromLiteralExpression(path) : literal.buildNode(path);
return {
arguments: [pathNode, child],
};
}

export function toElasticsearchQuery(node, indexPattern, config, context = {}) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const [path, child] = node.arguments;
const stringPath = ast.toElasticsearchQuery(path);
const fullPath =
context.nested && context.nested.path ? `${context.nested.path}.${stringPath}` : stringPath;
const fullPath = context?.nested?.path ? `${context.nested.path}.${stringPath}` : stringPath;

return {
nested: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@
*/

import * as ast from '../ast';
import { IIndexPattern, KueryNode } from '../../..';

export function buildNodeParams(child) {
export function buildNodeParams(child: KueryNode) {
return {
arguments: [child],
};
}

export function toElasticsearchQuery(node, indexPattern, config, context) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const [argument] = node.arguments;

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@
*/

import * as ast from '../ast';
import { IIndexPattern, KueryNode } from '../../..';

export function buildNodeParams(children) {
export function buildNodeParams(children: KueryNode[]) {
return {
arguments: children,
};
}

export function toElasticsearchQuery(node, indexPattern, config, context) {
export function toElasticsearchQuery(
node: KueryNode,
indexPattern?: IIndexPattern,
config: Record<string, any> = {},
context: Record<string, any> = {}
) {
const children = node.arguments || [];

return {
bool: {
should: children.map(child => {
should: children.map((child: KueryNode) => {
return ast.toElasticsearchQuery(child, indexPattern, config, context);
}),
minimum_should_match: 1,
Expand Down
Loading

0 comments on commit 3e491f8

Please sign in to comment.