Skip to content

Commit

Permalink
kql, lucene and timerange functions (elastic#93043)
Browse files Browse the repository at this point in the history
  • Loading branch information
ppisljar committed Mar 17, 2021
1 parent af1a838 commit 02aacc0
Show file tree
Hide file tree
Showing 20 changed files with 416 additions and 13 deletions.
2 changes: 1 addition & 1 deletion api_docs/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -27843,4 +27843,4 @@
}
]
}
}
}
2 changes: 1 addition & 1 deletion api_docs/data_search.json
Original file line number Diff line number Diff line change
Expand Up @@ -19470,4 +19470,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'],
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": "kuery",
"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: 'kuery',
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

0 comments on commit 02aacc0

Please sign in to comment.