[KQL] Fix node builder with empty array (#155901)

## Summary

Fixes https://github.com/elastic/kibana/issues/95657.

Updates the logic for `nodeBuilder` when passing an empty array to
`nodeBuilder.and`/`nodeBuilder.or` to actually return a `KueryNode`
instead of `undefined`.

### Checklist

- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Lukas Olson 2023-04-27 11:28:12 -07:00 committed by GitHub
parent 0858b388f4
commit 654287b3ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 6 deletions

View file

@ -55,6 +55,25 @@ describe('nodeBuilder', () => {
});
describe('and method', () => {
test('no clauses', () => {
const node = nodeBuilder.and([]);
const query = toElasticsearchQuery(node);
expect(node).toMatchInlineSnapshot(`
Object {
"arguments": Array [],
"function": "and",
"type": "function",
}
`);
expect(query).toMatchInlineSnapshot(`
Object {
"bool": Object {
"filter": Array [],
},
}
`);
});
test('single clause', () => {
const nodes = [nodeBuilder.is('foo', 'bar')];
const query = toElasticsearchQuery(nodeBuilder.and(nodes));
@ -166,6 +185,26 @@ describe('nodeBuilder', () => {
});
describe('or method', () => {
test('no clauses', () => {
const node = nodeBuilder.or([]);
const query = toElasticsearchQuery(node);
expect(node).toMatchInlineSnapshot(`
Object {
"arguments": Array [],
"function": "or",
"type": "function",
}
`);
expect(query).toMatchInlineSnapshot(`
Object {
"bool": Object {
"minimum_should_match": 1,
"should": Array [],
},
}
`);
});
test('single clause', () => {
const nodes = [nodeBuilder.is('foo', 'bar')];
const query = toElasticsearchQuery(nodeBuilder.or(nodes));

View file

@ -10,23 +10,23 @@ import type { RangeFilterParams } from '../../filters';
import { KueryNode, nodeTypes } from '../types';
export const nodeBuilder = {
is: (fieldName: string, value: string | KueryNode) => {
is: (fieldName: string, value: string | KueryNode): KueryNode => {
return nodeTypes.function.buildNodeWithArgumentNodes('is', [
nodeTypes.literal.buildNode(fieldName),
typeof value === 'string' ? nodeTypes.literal.buildNode(value) : value,
]);
},
or: (nodes: KueryNode[]): KueryNode => {
return nodes.length > 1 ? nodeTypes.function.buildNode('or', nodes) : nodes[0];
return nodes.length === 1 ? nodes[0] : nodeTypes.function.buildNode('or', nodes);
},
and: (nodes: KueryNode[]): KueryNode => {
return nodes.length > 1 ? nodeTypes.function.buildNode('and', nodes) : nodes[0];
return nodes.length === 1 ? nodes[0] : nodeTypes.function.buildNode('and', nodes);
},
range: (
fieldName: string,
operator: keyof Pick<RangeFilterParams, 'gt' | 'gte' | 'lt' | 'lte'>,
value: number | string
) => {
): KueryNode => {
return nodeTypes.function.buildNodeWithArgumentNodes('range', [
nodeTypes.literal.buildNode(fieldName),
operator,

View file

@ -575,7 +575,11 @@ describe('SearchSessionService', () => {
const [[findOptions]] = savedObjectsClient.find.mock.calls;
expect(findOptions).toMatchInlineSnapshot(`
Object {
"filter": undefined,
"filter": Object {
"arguments": Array [],
"function": "and",
"type": "function",
},
"page": 0,
"perPage": 5,
"type": "search-session",

View file

@ -61,6 +61,10 @@ describe('convertRuleIdsToKueryNode', () => {
});
test('should convert empty ids array correctly', () => {
expect(convertRuleIdsToKueryNode([])).toEqual(undefined);
expect(convertRuleIdsToKueryNode([])).toEqual({
arguments: [],
function: 'or',
type: 'function',
});
});
});