Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kql, lucene and timerange functions #93043

Merged
merged 15 commits into from
Mar 17, 2021
2 changes: 1 addition & 1 deletion api_docs/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -27594,4 +27594,4 @@
}
]
}
}
}
2 changes: 1 addition & 1 deletion api_docs/data_search.json
Original file line number Diff line number Diff line change
Expand Up @@ -19293,4 +19293,4 @@
}
]
}
}
}
2 changes: 1 addition & 1 deletion api_docs/expressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -33883,4 +33883,4 @@
}
]
}
}
}
5 changes: 5 additions & 0 deletions src/plugins/data/common/search/expressions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

export * from './kibana';
export * from './kibana_context';
export * from './kql';
export * from './lucene';
export * from './query_to_ast';
export * from './timerange_to_ast';
export * from './kibana_context_type';
export * from './esaggs';
export * from './utils';
export * from './timerange';
14 changes: 8 additions & 6 deletions src/plugins/data/common/search/expressions/kibana_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expr
import { Adapters } from 'src/plugins/inspector/common';
import { Query, uniqFilters } from '../../query';
import { ExecutionContextSearch, KibanaContext } from './kibana_context_type';
import { KibanaQueryOutput } from './kibana_context_type';
import { KibanaTimerangeOutput } from './timerange';

interface Arguments {
q?: string | null;
q?: KibanaQueryOutput | null;
filters?: string | null;
timeRange?: string | null;
timeRange?: KibanaTimerangeOutput | null;
savedSearchId?: string | null;
}

Expand Down Expand Up @@ -46,7 +48,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = {
}),
args: {
q: {
types: ['string', 'null'],
types: ['kibana_query', 'null'],
aliases: ['query', '_'],
default: null,
help: i18n.translate('data.search.functions.kibana_context.q.help', {
Expand All @@ -61,7 +63,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = {
}),
},
timeRange: {
types: ['string', 'null'],
types: ['timerange', 'null'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these two type changes are breaking changes, so we need a breaking change note.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we never store the expression (except in canvas), nor expose expression to the user (except in canvas) and this function is not used in canvas I think we are good

default: null,
help: i18n.translate('data.search.functions.kibana_context.timeRange.help', {
defaultMessage: 'Specify Kibana time range filter',
Expand All @@ -77,8 +79,8 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = {
},

async fn(input, args, { getSavedObject }) {
const timeRange = getParsedValue(args.timeRange, input?.timeRange);
let queries = mergeQueries(input?.query, getParsedValue(args?.q, []));
const timeRange = args.timeRange || input?.timeRange;
let queries = mergeQueries(input?.query, args?.q || []);
let filters = [...(input?.filters || []), ...getParsedValue(args?.filters, [])];

if (args.savedSearchId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type ExpressionValueSearchContext = ExpressionValueBoxed<
ExecutionContextSearch
>;

export type KibanaQueryOutput = ExpressionValueBoxed<'kibana_query', Query>;

// TODO: These two are exported for legacy reasons - remove them eventually.
export type KIBANA_CONTEXT_NAME = 'kibana_context';
export type KibanaContext = ExpressionValueSearchContext;
Expand Down
43 changes: 43 additions & 0 deletions src/plugins/data/common/search/expressions/kql.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { ExecutionContext } from 'src/plugins/expressions/common';
import { ExpressionValueSearchContext } from './kibana_context_type';
import { functionWrapper } from './utils';
import { kqlFunction } from './kql';

describe('interpreter/functions#kql', () => {
const fn = functionWrapper(kqlFunction);
let input: Partial<ExpressionValueSearchContext>;
let context: ExecutionContext;

beforeEach(() => {
input = { timeRange: { from: '0', to: '1' } };
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,
inspectorAdapters: {} as any,
};
});

it('returns an object with the correct structure', () => {
const actual = fn(input, { q: 'test' }, context);
expect(actual).toMatchInlineSnapshot(
`
Object {
"language": "kql",
"query": "test",
"type": "kibana_query",
}
`
);
});
});
49 changes: 49 additions & 0 deletions src/plugins/data/common/search/expressions/kql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { KibanaQueryOutput } from './kibana_context_type';

interface Arguments {
q: string;
}

export type ExpressionFunctionKql = ExpressionFunctionDefinition<
'kql',
null,
Arguments,
KibanaQueryOutput
>;

export const kqlFunction: ExpressionFunctionKql = {
name: 'kql',
type: 'kibana_query',
inputTypes: ['null'],
help: i18n.translate('data.search.functions.kql.help', {
defaultMessage: 'Create kibana kql query',
}),
args: {
q: {
types: ['string'],
required: true,
aliases: ['query', '_'],
help: i18n.translate('data.search.functions.kql.q.help', {
defaultMessage: 'Specify Kibana KQL free form text query',
}),
},
},

fn(input, args) {
return {
type: 'kibana_query',
language: 'kql',
query: args.q,
};
},
};
43 changes: 43 additions & 0 deletions src/plugins/data/common/search/expressions/lucene.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { ExecutionContext } from 'src/plugins/expressions/common';
import { ExpressionValueSearchContext } from './kibana_context_type';
import { functionWrapper } from './utils';
import { luceneFunction } from './lucene';

describe('interpreter/functions#lucene', () => {
const fn = functionWrapper(luceneFunction);
let input: Partial<ExpressionValueSearchContext>;
let context: ExecutionContext;

beforeEach(() => {
input = { timeRange: { from: '0', to: '1' } };
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,
inspectorAdapters: {} as any,
};
});

it('returns an object with the correct structure', () => {
const actual = fn(input, { q: '{ "test": 1 }' }, context);
expect(actual).toMatchInlineSnapshot(`
Object {
"language": "lucene",
"query": Object {
"test": 1,
},
"type": "kibana_query",
}
`);
});
});
49 changes: 49 additions & 0 deletions src/plugins/data/common/search/expressions/lucene.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { KibanaQueryOutput } from './kibana_context_type';

interface Arguments {
q: string;
}

export type ExpressionFunctionLucene = ExpressionFunctionDefinition<
'lucene',
null,
Arguments,
KibanaQueryOutput
>;

export const luceneFunction: ExpressionFunctionLucene = {
name: 'lucene',
type: 'kibana_query',
inputTypes: ['null'],
help: i18n.translate('data.search.functions.lucene.help', {
defaultMessage: 'Create kibana lucene query',
}),
args: {
q: {
types: ['string'],
required: true,
aliases: ['query', '_'],
help: i18n.translate('data.search.functions.lucene.q.help', {
defaultMessage: 'Specify Lucene free form text query',
}),
},
},

fn(input, args) {
return {
type: 'kibana_query',
language: 'lucene',
query: JSON.parse(args.q),
};
},
};
29 changes: 29 additions & 0 deletions src/plugins/data/common/search/expressions/query_to_ast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { queryToAst } from './query_to_ast';

describe('queryToAst', () => {
it('returns an object with the correct structure for lucene queies', () => {
const actual = queryToAst({ language: 'lucene', query: { country: 'US' } });
expect(actual).toHaveProperty('functions');
expect(actual.functions[0]).toHaveProperty('name', 'lucene');
expect(actual.functions[0]).toHaveProperty('arguments', {
q: ['{"country":"US"}'],
});
});

it('returns an object with the correct structure for kql queies', () => {
const actual = queryToAst({ language: 'kuery', query: 'country:US' });
expect(actual).toHaveProperty('functions');
expect(actual.functions[0]).toHaveProperty('name', 'kql');
expect(actual.functions[0]).toHaveProperty('arguments', {
q: ['country:US'],
});
});
});
23 changes: 23 additions & 0 deletions src/plugins/data/common/search/expressions/query_to_ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { buildExpression, buildExpressionFunction } from '../../../../expressions/common';
import { Query } from '../../query';
import { ExpressionFunctionKql } from './kql';
import { ExpressionFunctionLucene } from './lucene';

export const queryToAst = (query: Query) => {
if (query.language === 'kuery') {
return buildExpression([
buildExpressionFunction<ExpressionFunctionKql>('kql', { q: query.query as string }),
]);
}
return buildExpression([
buildExpressionFunction<ExpressionFunctionLucene>('lucene', { q: JSON.stringify(query.query) }),
]);
};
44 changes: 44 additions & 0 deletions src/plugins/data/common/search/expressions/timerange.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { ExecutionContext } from 'src/plugins/expressions/common';
import { ExpressionValueSearchContext } from './kibana_context_type';
import { functionWrapper } from './utils';
import { kibanaTimerangeFunction } from './timerange';

describe('interpreter/functions#timerange', () => {
const fn = functionWrapper(kibanaTimerangeFunction);
let input: Partial<ExpressionValueSearchContext>;
let context: ExecutionContext;

beforeEach(() => {
input = { timeRange: { from: '0', to: '1' } };
context = {
getSearchContext: () => ({}),
getSearchSessionId: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,
inspectorAdapters: {} as any,
};
});

it('returns an object with the correct structure', () => {
const actual = fn(input, { from: 'now', to: 'now-7d', mode: 'absolute' }, context);
expect(actual).toMatchInlineSnapshot(
`
Object {
"from": "now",
"mode": "absolute",
"to": "now-7d",
"type": "timerange",
}
`
);
});
});
Loading