adding kibana filter expression functions (#94069)

This commit is contained in:
Peter Pisljar 2021-03-23 18:29:26 +01:00 committed by GitHub
parent adeda3991d
commit edb9453a83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 851 additions and 10 deletions

View file

@ -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;
}

View file

@ -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",
}
`);
});
});

View 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
),
};
},
};

View 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",
}
`);
});
});

View 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;
},
};

View file

@ -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\\"}}",
],
}
`);
});
});

View 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,
}),
]);
});
};

View file

@ -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';

View file

@ -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') {

View file

@ -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';

View file

@ -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",
}
`);
});
});

View 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),
};
},
};

View file

@ -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",
}
`);
});
});

View 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
),
};
},
};

View 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",
}
`);
});
});

View 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,
};
},
};

View file

@ -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",
}
`);
});
});

View 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
),
};
},
};

View file

@ -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);

View file

@ -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 });

View file

@ -11,3 +11,4 @@ export * from './get_type';
export * from './serialize_provider';
export * from './expression_type';
export * from './specs';
export * from './unbox_expression_value';

View file

@ -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' });
});
});

View file

@ -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;
}

View file

@ -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';

View 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;
};

View file

@ -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,
});