adding kibana filter expression functions (#94069)
This commit is contained in:
parent
adeda3991d
commit
edb9453a83
|
@ -26,13 +26,15 @@ export function buildFilter(
|
|||
disabled: boolean,
|
||||
params: any,
|
||||
alias: string | null,
|
||||
store: FilterStateStore
|
||||
store?: FilterStateStore
|
||||
): Filter {
|
||||
const filter = buildBaseFilter(indexPattern, field, type, params);
|
||||
filter.meta.alias = alias;
|
||||
filter.meta.negate = negate;
|
||||
filter.meta.disabled = disabled;
|
||||
filter.$state = { store };
|
||||
if (store) {
|
||||
filter.$state = { store };
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { createMockContext } from '../../../../expressions/common';
|
||||
import { functionWrapper } from './utils';
|
||||
import { existsFilterFunction } from './exists_filter';
|
||||
|
||||
describe('interpreter/functions#existsFilter', () => {
|
||||
const fn = functionWrapper(existsFilterFunction);
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(null, { field: { spec: { name: 'test' } } }, createMockContext());
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "test",
|
||||
},
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"disabled": false,
|
||||
"index": undefined,
|
||||
"negate": false,
|
||||
},
|
||||
"type": "kibana_filter",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
65
src/plugins/data/common/search/expressions/exists_filter.ts
Normal file
65
src/plugins/data/common/search/expressions/exists_filter.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { KibanaField, KibanaFilter } from './kibana_context_type';
|
||||
import { buildFilter, FILTERS } from '../../es_query/filters';
|
||||
import { IndexPattern } from '../../index_patterns/index_patterns';
|
||||
|
||||
interface Arguments {
|
||||
field: KibanaField;
|
||||
negate?: boolean;
|
||||
}
|
||||
|
||||
export type ExpressionFunctionExistsFilter = ExpressionFunctionDefinition<
|
||||
'existsFilter',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaFilter
|
||||
>;
|
||||
|
||||
export const existsFilterFunction: ExpressionFunctionExistsFilter = {
|
||||
name: 'existsFilter',
|
||||
type: 'kibana_filter',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.existsFilter.help', {
|
||||
defaultMessage: 'Create kibana exists filter',
|
||||
}),
|
||||
args: {
|
||||
field: {
|
||||
types: ['kibana_field'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.existsFilter.field.help', {
|
||||
defaultMessage: 'Specify the field you want to filter on. Use `field` function.',
|
||||
}),
|
||||
},
|
||||
negate: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('data.search.functions.existsFilter.negate.help', {
|
||||
defaultMessage: 'Should the filter be negated.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'kibana_filter',
|
||||
...buildFilter(
|
||||
({} as any) as IndexPattern,
|
||||
args.field.spec,
|
||||
FILTERS.EXISTS,
|
||||
args.negate || false,
|
||||
false,
|
||||
{},
|
||||
null
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
42
src/plugins/data/common/search/expressions/field.test.ts
Normal file
42
src/plugins/data/common/search/expressions/field.test.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { functionWrapper } from './utils';
|
||||
import { fieldFunction } from './field';
|
||||
|
||||
describe('interpreter/functions#field', () => {
|
||||
const fn = functionWrapper(fieldFunction);
|
||||
let context: ExecutionContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
getSearchContext: () => ({}),
|
||||
getSearchSessionId: () => undefined,
|
||||
types: {},
|
||||
variables: {},
|
||||
abortSignal: {} as any,
|
||||
inspectorAdapters: {} as any,
|
||||
};
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(null, { name: 'test', type: 'number' }, context);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"spec": Object {
|
||||
"name": "test",
|
||||
"script": undefined,
|
||||
"scripted": false,
|
||||
"type": "number",
|
||||
},
|
||||
"type": "kibana_field",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
67
src/plugins/data/common/search/expressions/field.ts
Normal file
67
src/plugins/data/common/search/expressions/field.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { KibanaField } from './kibana_context_type';
|
||||
|
||||
interface Arguments {
|
||||
name: string;
|
||||
type: string;
|
||||
script?: string;
|
||||
}
|
||||
|
||||
export type ExpressionFunctionField = ExpressionFunctionDefinition<
|
||||
'field',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaField
|
||||
>;
|
||||
|
||||
export const fieldFunction: ExpressionFunctionField = {
|
||||
name: 'field',
|
||||
type: 'kibana_field',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.field.help', {
|
||||
defaultMessage: 'Create a Kibana field.',
|
||||
}),
|
||||
args: {
|
||||
name: {
|
||||
types: ['string'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.field.name.help', {
|
||||
defaultMessage: 'Name of the field',
|
||||
}),
|
||||
},
|
||||
type: {
|
||||
types: ['string'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.field.type.help', {
|
||||
defaultMessage: 'Type of the field',
|
||||
}),
|
||||
},
|
||||
script: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.functions.field.script.help', {
|
||||
defaultMessage: 'A field script, in case the field is scripted.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'kibana_field',
|
||||
spec: {
|
||||
name: args.name,
|
||||
type: args.type,
|
||||
scripted: args.script ? true : false,
|
||||
script: args.script,
|
||||
},
|
||||
} as KibanaField;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { filtersToAst } from './filters_to_ast';
|
||||
|
||||
describe('interpreter/functions#filtersToAst', () => {
|
||||
const normalFilter = {
|
||||
meta: { negate: false, alias: '', disabled: false },
|
||||
query: { test: 'something' },
|
||||
};
|
||||
const negatedFilter = {
|
||||
meta: { negate: true, alias: '', disabled: false },
|
||||
query: { test: 'something' },
|
||||
};
|
||||
|
||||
it('converts a list of filters to an expression AST node', () => {
|
||||
const actual = filtersToAst([normalFilter, negatedFilter]);
|
||||
expect(actual).toHaveLength(2);
|
||||
expect(actual[0].functions[0]).toHaveProperty('name', 'kibanaFilter');
|
||||
expect(actual[0].functions[0].arguments).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"negate": Array [
|
||||
false,
|
||||
],
|
||||
"query": Array [
|
||||
"{\\"query\\":{\\"test\\":\\"something\\"}}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(actual[1].functions[0]).toHaveProperty('name', 'kibanaFilter');
|
||||
expect(actual[1].functions[0].arguments).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"negate": Array [
|
||||
true,
|
||||
],
|
||||
"query": Array [
|
||||
"{\\"query\\":{\\"test\\":\\"something\\"}}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
23
src/plugins/data/common/search/expressions/filters_to_ast.ts
Normal file
23
src/plugins/data/common/search/expressions/filters_to_ast.ts
Normal file
|
@ -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 { Filter } from '../../es_query/filters';
|
||||
import { ExpressionFunctionKibanaFilter } from './kibana_filter';
|
||||
|
||||
export const filtersToAst = (filters: Filter[] | Filter) => {
|
||||
return (Array.isArray(filters) ? filters : [filters]).map((filter) => {
|
||||
const { meta, $state, ...restOfFilter } = filter;
|
||||
return buildExpression([
|
||||
buildExpressionFunction<ExpressionFunctionKibanaFilter>('kibanaFilter', {
|
||||
query: JSON.stringify(restOfFilter),
|
||||
negate: filter.meta.negate,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
};
|
|
@ -15,4 +15,11 @@ export * from './timerange_to_ast';
|
|||
export * from './kibana_context_type';
|
||||
export * from './esaggs';
|
||||
export * from './utils';
|
||||
export * from './range';
|
||||
export * from './field';
|
||||
export * from './phrase_filter';
|
||||
export * from './exists_filter';
|
||||
export * from './range_filter';
|
||||
export * from './kibana_filter';
|
||||
export * from './filters_to_ast';
|
||||
export * from './timerange';
|
||||
|
|
|
@ -10,14 +10,15 @@ import { uniqBy } from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expressions/common';
|
||||
import { Adapters } from 'src/plugins/inspector/common';
|
||||
import { unboxExpressionValue } from '../../../../expressions/common';
|
||||
import { Query, uniqFilters } from '../../query';
|
||||
import { ExecutionContextSearch, KibanaContext } from './kibana_context_type';
|
||||
import { ExecutionContextSearch, KibanaContext, KibanaFilter } from './kibana_context_type';
|
||||
import { KibanaQueryOutput } from './kibana_context_type';
|
||||
import { KibanaTimerangeOutput } from './timerange';
|
||||
|
||||
interface Arguments {
|
||||
q?: KibanaQueryOutput | null;
|
||||
filters?: string | null;
|
||||
filters?: KibanaFilter[] | null;
|
||||
timeRange?: KibanaTimerangeOutput | null;
|
||||
savedSearchId?: string | null;
|
||||
}
|
||||
|
@ -56,8 +57,8 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = {
|
|||
}),
|
||||
},
|
||||
filters: {
|
||||
types: ['string', 'null'],
|
||||
default: '"[]"',
|
||||
types: ['kibana_filter', 'null'],
|
||||
multi: true,
|
||||
help: i18n.translate('data.search.functions.kibana_context.filters.help', {
|
||||
defaultMessage: 'Specify Kibana generic filters',
|
||||
}),
|
||||
|
@ -81,7 +82,7 @@ export const kibanaContextFunction: ExpressionFunctionKibanaContext = {
|
|||
async fn(input, args, { getSavedObject }) {
|
||||
const timeRange = args.timeRange || input?.timeRange;
|
||||
let queries = mergeQueries(input?.query, args?.q || []);
|
||||
let filters = [...(input?.filters || []), ...getParsedValue(args?.filters, [])];
|
||||
let filters = [...(input?.filters || []), ...(args?.filters?.map(unboxExpressionValue) || [])];
|
||||
|
||||
if (args.savedSearchId) {
|
||||
if (typeof getSavedObject !== 'function') {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { ExpressionValueBoxed } from 'src/plugins/expressions/common';
|
||||
import { Filter } from '../../es_query';
|
||||
import { Query, TimeRange } from '../../query';
|
||||
import { IndexPatternField } from '../../index_patterns/fields';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type ExecutionContextSearch = {
|
||||
|
@ -23,6 +24,8 @@ export type ExpressionValueSearchContext = ExpressionValueBoxed<
|
|||
>;
|
||||
|
||||
export type KibanaQueryOutput = ExpressionValueBoxed<'kibana_query', Query>;
|
||||
export type KibanaFilter = ExpressionValueBoxed<'kibana_filter', Filter>;
|
||||
export type KibanaField = ExpressionValueBoxed<'kibana_field', IndexPatternField>;
|
||||
|
||||
// TODO: These two are exported for legacy reasons - remove them eventually.
|
||||
export type KIBANA_CONTEXT_NAME = 'kibana_context';
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { createMockContext } from '../../../../expressions/common';
|
||||
import { functionWrapper } from './utils';
|
||||
import { kibanaFilterFunction } from './kibana_filter';
|
||||
|
||||
describe('interpreter/functions#kibanaFilter', () => {
|
||||
const fn = functionWrapper(kibanaFilterFunction);
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(null, { query: '{ "name": "test" }' }, createMockContext());
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"alias": "",
|
||||
"disabled": false,
|
||||
"negate": false,
|
||||
},
|
||||
"name": "test",
|
||||
"type": "kibana_filter",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
61
src/plugins/data/common/search/expressions/kibana_filter.ts
Normal file
61
src/plugins/data/common/search/expressions/kibana_filter.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { KibanaFilter } from './kibana_context_type';
|
||||
|
||||
interface Arguments {
|
||||
query: string;
|
||||
negate?: boolean;
|
||||
}
|
||||
|
||||
export type ExpressionFunctionKibanaFilter = ExpressionFunctionDefinition<
|
||||
'kibanaFilter',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaFilter
|
||||
>;
|
||||
|
||||
export const kibanaFilterFunction: ExpressionFunctionKibanaFilter = {
|
||||
name: 'kibanaFilter',
|
||||
type: 'kibana_filter',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.kibanaFilter.help', {
|
||||
defaultMessage: 'Create kibana filter',
|
||||
}),
|
||||
args: {
|
||||
query: {
|
||||
types: ['string'],
|
||||
aliases: ['q', '_'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.kibanaFilter.field.help', {
|
||||
defaultMessage: 'Specify free form esdsl query',
|
||||
}),
|
||||
},
|
||||
negate: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('data.search.functions.kibanaFilter.negate.help', {
|
||||
defaultMessage: 'Should the filter be negated',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'kibana_filter',
|
||||
meta: {
|
||||
negate: args.negate || false,
|
||||
alias: '',
|
||||
disabled: false,
|
||||
},
|
||||
...JSON.parse(args.query),
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { createMockContext } from '../../../../expressions/common';
|
||||
import { functionWrapper } from './utils';
|
||||
import { phraseFilterFunction } from './phrase_filter';
|
||||
|
||||
describe('interpreter/functions#phraseFilter', () => {
|
||||
const fn = functionWrapper(phraseFilterFunction);
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(
|
||||
null,
|
||||
{ field: { spec: { name: 'test' } }, phrase: ['test', 'something'] },
|
||||
createMockContext()
|
||||
);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"disabled": false,
|
||||
"index": undefined,
|
||||
"key": "test",
|
||||
"negate": false,
|
||||
"params": Array [
|
||||
"test",
|
||||
"something",
|
||||
],
|
||||
"type": "phrases",
|
||||
"value": "test, something",
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"test": "something",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"type": "kibana_filter",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
89
src/plugins/data/common/search/expressions/phrase_filter.ts
Normal file
89
src/plugins/data/common/search/expressions/phrase_filter.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 { KibanaField, KibanaFilter } from './kibana_context_type';
|
||||
import { buildFilter, FILTERS } from '../../es_query/filters';
|
||||
import { IndexPattern } from '../../index_patterns/index_patterns';
|
||||
|
||||
interface Arguments {
|
||||
field: KibanaField;
|
||||
phrase: string[];
|
||||
negate?: boolean;
|
||||
}
|
||||
|
||||
export type ExpressionFunctionPhraseFilter = ExpressionFunctionDefinition<
|
||||
'rangeFilter',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaFilter
|
||||
>;
|
||||
|
||||
export const phraseFilterFunction: ExpressionFunctionPhraseFilter = {
|
||||
name: 'rangeFilter',
|
||||
type: 'kibana_filter',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.phraseFilter.help', {
|
||||
defaultMessage: 'Create kibana phrase or phrases filter',
|
||||
}),
|
||||
args: {
|
||||
field: {
|
||||
types: ['kibana_field'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.phraseFilter.field.help', {
|
||||
defaultMessage: 'Specify the field you want to filter on. Use `field` function.',
|
||||
}),
|
||||
},
|
||||
phrase: {
|
||||
types: ['string'],
|
||||
multi: true,
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.phraseFilter.phrase.help', {
|
||||
defaultMessage: 'Specify the phrases',
|
||||
}),
|
||||
},
|
||||
negate: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('data.search.functions.phraseFilter.negate.help', {
|
||||
defaultMessage: 'Should the filter be negated',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
if (args.phrase.length === 1) {
|
||||
return {
|
||||
type: 'kibana_filter',
|
||||
...buildFilter(
|
||||
({} as any) as IndexPattern,
|
||||
args.field.spec,
|
||||
FILTERS.PHRASE,
|
||||
args.negate || false,
|
||||
false,
|
||||
args.phrase[0],
|
||||
null
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'kibana_filter',
|
||||
...buildFilter(
|
||||
({} as any) as IndexPattern,
|
||||
args.field.spec,
|
||||
FILTERS.PHRASES,
|
||||
args.negate || false,
|
||||
false,
|
||||
args.phrase,
|
||||
null
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
40
src/plugins/data/common/search/expressions/range.test.ts
Normal file
40
src/plugins/data/common/search/expressions/range.test.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { functionWrapper } from './utils';
|
||||
import { rangeFunction } from './range';
|
||||
|
||||
describe('interpreter/functions#range', () => {
|
||||
const fn = functionWrapper(rangeFunction);
|
||||
let context: ExecutionContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
getSearchContext: () => ({}),
|
||||
getSearchSessionId: () => undefined,
|
||||
types: {},
|
||||
variables: {},
|
||||
abortSignal: {} as any,
|
||||
inspectorAdapters: {} as any,
|
||||
};
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(null, { lt: 20, gt: 10 }, context);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"gt": 10,
|
||||
"gte": undefined,
|
||||
"lt": 20,
|
||||
"lte": undefined,
|
||||
"type": "kibana_range",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
76
src/plugins/data/common/search/expressions/range.ts
Normal file
76
src/plugins/data/common/search/expressions/range.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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, ExpressionValueBoxed } from 'src/plugins/expressions/common';
|
||||
|
||||
interface Arguments {
|
||||
gt?: number | string;
|
||||
lt?: number | string;
|
||||
gte?: number | string;
|
||||
lte?: number | string;
|
||||
}
|
||||
|
||||
export type KibanaRange = ExpressionValueBoxed<'kibana_range', Arguments>;
|
||||
|
||||
export type ExpressionFunctionRange = ExpressionFunctionDefinition<
|
||||
'range',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaRange
|
||||
>;
|
||||
|
||||
export const rangeFunction: ExpressionFunctionRange = {
|
||||
name: 'range',
|
||||
type: 'kibana_range',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.range.help', {
|
||||
defaultMessage: 'Create kibana range filter',
|
||||
}),
|
||||
args: {
|
||||
gt: {
|
||||
types: ['string', 'number'],
|
||||
help: i18n.translate('data.search.functions.range.gt.help', {
|
||||
defaultMessage: 'Greater than',
|
||||
}),
|
||||
},
|
||||
lt: {
|
||||
types: ['string', 'number'],
|
||||
help: i18n.translate('data.search.functions.range.lt.help', {
|
||||
defaultMessage: 'Less than',
|
||||
}),
|
||||
},
|
||||
gte: {
|
||||
types: ['string', 'number'],
|
||||
help: i18n.translate('data.search.functions.range.gte.help', {
|
||||
defaultMessage: 'Greater or equal than',
|
||||
}),
|
||||
},
|
||||
lte: {
|
||||
types: ['string', 'number'],
|
||||
help: i18n.translate('data.search.functions.range.lte.help', {
|
||||
defaultMessage: 'Less or equal than',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
if (args.lt === undefined && args.lte === undefined) {
|
||||
throw new Error('lt or lte must be provided');
|
||||
}
|
||||
|
||||
if (args.gt === undefined && args.gte === undefined) {
|
||||
throw new Error('gt or gte must be provided');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'kibana_range',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { createMockContext } from '../../../../expressions/common';
|
||||
import { functionWrapper } from './utils';
|
||||
import { rangeFilterFunction } from './range_filter';
|
||||
|
||||
describe('interpreter/functions#rangeFilter', () => {
|
||||
const fn = functionWrapper(rangeFilterFunction);
|
||||
|
||||
it('returns an object with the correct structure', () => {
|
||||
const actual = fn(
|
||||
null,
|
||||
{ field: { spec: { name: 'test' } }, range: { gt: 10, lt: 20 } },
|
||||
createMockContext()
|
||||
);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"alias": null,
|
||||
"disabled": false,
|
||||
"index": undefined,
|
||||
"negate": false,
|
||||
"params": Object {},
|
||||
},
|
||||
"range": Object {
|
||||
"test": Object {
|
||||
"gte": 10,
|
||||
"lt": 20,
|
||||
},
|
||||
},
|
||||
"type": "kibana_filter",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
74
src/plugins/data/common/search/expressions/range_filter.ts
Normal file
74
src/plugins/data/common/search/expressions/range_filter.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { KibanaField, KibanaFilter } from './kibana_context_type';
|
||||
import { buildFilter, FILTERS } from '../../es_query/filters';
|
||||
import { IndexPattern } from '../../index_patterns/index_patterns';
|
||||
import { KibanaRange } from './range';
|
||||
|
||||
interface Arguments {
|
||||
field: KibanaField;
|
||||
range: KibanaRange;
|
||||
negate?: boolean;
|
||||
}
|
||||
|
||||
export type ExpressionFunctionRangeFilter = ExpressionFunctionDefinition<
|
||||
'rangeFilter',
|
||||
null,
|
||||
Arguments,
|
||||
KibanaFilter
|
||||
>;
|
||||
|
||||
export const rangeFilterFunction: ExpressionFunctionRangeFilter = {
|
||||
name: 'rangeFilter',
|
||||
type: 'kibana_filter',
|
||||
inputTypes: ['null'],
|
||||
help: i18n.translate('data.search.functions.rangeFilter.help', {
|
||||
defaultMessage: 'Create kibana range filter',
|
||||
}),
|
||||
args: {
|
||||
field: {
|
||||
types: ['kibana_field'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.rangeFilter.field.help', {
|
||||
defaultMessage: 'Specify the field you want to filter on. Use `field` function.',
|
||||
}),
|
||||
},
|
||||
range: {
|
||||
types: ['kibana_range'],
|
||||
required: true,
|
||||
help: i18n.translate('data.search.functions.rangeFilter.range.help', {
|
||||
defaultMessage: 'Specify the range, use `range` function.',
|
||||
}),
|
||||
},
|
||||
negate: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('data.search.functions.rangeFilter.negate.help', {
|
||||
defaultMessage: 'Should the filter be negated',
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'kibana_filter',
|
||||
...buildFilter(
|
||||
({} as any) as IndexPattern,
|
||||
args.field.spec,
|
||||
FILTERS.RANGE,
|
||||
args.negate || false,
|
||||
false,
|
||||
{ from: args.range.gt || args.range.gte, to: args.range.lt || args.range.lte },
|
||||
null
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
|
@ -28,6 +28,12 @@ import {
|
|||
kibanaTimerangeFunction,
|
||||
luceneFunction,
|
||||
kqlFunction,
|
||||
fieldFunction,
|
||||
rangeFunction,
|
||||
existsFilterFunction,
|
||||
rangeFilterFunction,
|
||||
kibanaFilterFunction,
|
||||
phraseFilterFunction,
|
||||
} from '../../common/search';
|
||||
import { getCallMsearch } from './legacy';
|
||||
import { AggsService, AggsStartDependencies } from './aggs';
|
||||
|
@ -108,6 +114,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
expressions.registerFunction(luceneFunction);
|
||||
expressions.registerFunction(kqlFunction);
|
||||
expressions.registerFunction(kibanaTimerangeFunction);
|
||||
expressions.registerFunction(fieldFunction);
|
||||
expressions.registerFunction(rangeFunction);
|
||||
expressions.registerFunction(kibanaFilterFunction);
|
||||
expressions.registerFunction(existsFilterFunction);
|
||||
expressions.registerFunction(rangeFilterFunction);
|
||||
expressions.registerFunction(phraseFilterFunction);
|
||||
expressions.registerType(kibanaContext);
|
||||
|
||||
expressions.registerFunction(esdsl);
|
||||
|
|
|
@ -43,6 +43,8 @@ import { registerUsageCollector } from './collectors/register';
|
|||
import { usageProvider } from './collectors/usage';
|
||||
import { searchTelemetry } from '../saved_objects';
|
||||
import {
|
||||
existsFilterFunction,
|
||||
fieldFunction,
|
||||
IEsSearchRequest,
|
||||
IEsSearchResponse,
|
||||
IKibanaSearchRequest,
|
||||
|
@ -52,11 +54,15 @@ import {
|
|||
kibanaContext,
|
||||
kibanaContextFunction,
|
||||
kibanaTimerangeFunction,
|
||||
kibanaFilterFunction,
|
||||
kqlFunction,
|
||||
luceneFunction,
|
||||
rangeFilterFunction,
|
||||
rangeFunction,
|
||||
SearchSourceDependencies,
|
||||
searchSourceRequiredUiSettings,
|
||||
SearchSourceService,
|
||||
phraseFilterFunction,
|
||||
} from '../../common/search';
|
||||
import { getEsaggs } from './expressions';
|
||||
import {
|
||||
|
@ -149,6 +155,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
expressions.registerFunction(kqlFunction);
|
||||
expressions.registerFunction(kibanaTimerangeFunction);
|
||||
expressions.registerFunction(kibanaContextFunction);
|
||||
expressions.registerFunction(fieldFunction);
|
||||
expressions.registerFunction(rangeFunction);
|
||||
expressions.registerFunction(kibanaFilterFunction);
|
||||
expressions.registerFunction(existsFilterFunction);
|
||||
expressions.registerFunction(rangeFilterFunction);
|
||||
expressions.registerFunction(phraseFilterFunction);
|
||||
expressions.registerType(kibanaContext);
|
||||
|
||||
const aggs = this.aggsService.setup({ registerFunction: expressions.registerFunction });
|
||||
|
|
|
@ -11,3 +11,4 @@ export * from './get_type';
|
|||
export * from './serialize_provider';
|
||||
export * from './expression_type';
|
||||
export * from './specs';
|
||||
export * from './unbox_expression_value';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { unboxExpressionValue } from './unbox_expression_value';
|
||||
|
||||
describe('unboxExpressionValue()', () => {
|
||||
it('should remove type property from a boxed value', () => {
|
||||
const expressionValue = { type: 'something', value: 'something' };
|
||||
|
||||
expect(unboxExpressionValue(expressionValue)).toEqual({ value: 'something' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { ExpressionValueBoxed } from './types';
|
||||
|
||||
export function unboxExpressionValue<T extends object>({
|
||||
type,
|
||||
...value
|
||||
}: ExpressionValueBoxed<string, T>): T {
|
||||
return value as T;
|
||||
}
|
|
@ -10,3 +10,4 @@ export * from './create_error';
|
|||
export * from './get_by_alias';
|
||||
export * from './tables_adapter';
|
||||
export * from './expressions_inspector_adapter';
|
||||
export * from './test_utils';
|
||||
|
|
20
src/plugins/expressions/common/util/test_utils.ts
Normal file
20
src/plugins/expressions/common/util/test_utils.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 '../execution';
|
||||
|
||||
export const createMockContext = () => {
|
||||
return {
|
||||
getSearchContext: () => ({}),
|
||||
getSearchSessionId: () => undefined,
|
||||
types: {},
|
||||
variables: {},
|
||||
abortSignal: {} as any,
|
||||
inspectorAdapters: {} as any,
|
||||
} as ExecutionContext;
|
||||
};
|
|
@ -10,7 +10,7 @@ import { ExpressionFunctionKibana, ExpressionFunctionKibanaContext } from '../..
|
|||
import { buildExpression, buildExpressionFunction } from '../../../expressions/public';
|
||||
|
||||
import { VisToExpressionAst } from '../types';
|
||||
import { queryToAst } from '../../../data/common';
|
||||
import { queryToAst, filtersToAst } from '../../../data/common';
|
||||
|
||||
/**
|
||||
* Creates an ast expression for a visualization based on kibana context (query, filters, timerange)
|
||||
|
@ -22,12 +22,15 @@ import { queryToAst } from '../../../data/common';
|
|||
export const toExpressionAst: VisToExpressionAst = async (vis, params) => {
|
||||
const { savedSearchId, searchSource } = vis.data;
|
||||
const query = searchSource?.getField('query');
|
||||
const filters = searchSource?.getField('filter');
|
||||
let filters = searchSource?.getField('filter');
|
||||
if (typeof filters === 'function') {
|
||||
filters = filters();
|
||||
}
|
||||
|
||||
const kibana = buildExpressionFunction<ExpressionFunctionKibana>('kibana', {});
|
||||
const kibanaContext = buildExpressionFunction<ExpressionFunctionKibanaContext>('kibana_context', {
|
||||
q: query && queryToAst(query),
|
||||
filters: filters && JSON.stringify(filters),
|
||||
filters: filters && filtersToAst(filters),
|
||||
savedSearchId,
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue