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

adding kibana filter expression functions #94069

Merged
merged 9 commits into from
Mar 23, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
adding kibanaFilter, rangeFilter, existsFilter and phraseFilter expre…
…ssion functions
  • Loading branch information
ppisljar committed Mar 22, 2021
commit 6fed6da7a0686f46d8ec7dca560c23610892112f
6 changes: 4 additions & 2 deletions src/plugins/data/common/es_query/filters/build_filters.ts
Original file line number Diff line number Diff line change
@@ -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;
}

45 changes: 45 additions & 0 deletions src/plugins/data/common/search/expressions/exists_filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { existsFilterFunction } from './exists_filter';

describe('interpreter/functions#existsFilter', () => {
const fn = functionWrapper(existsFilterFunction);
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, { field: { spec: { name: 'test' } } }, context);
Copy link
Contributor

@streamich streamich Mar 23, 2021

Choose a reason for hiding this comment

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

I understand this is copy-paste from similar tests in this plugin, but I've seen it a lot and wanted to suggest a small improvement. It is better to not create global module variables, like context here and not use beforeEach utility, which is specific to Jest. This can be done like so:

Suggested change
const actual = fn(null, { field: { spec: { name: 'test' } } }, context);
const context = setup();
const actual = fn(null, { field: { spec: { name: 'test' } } }, context);

Simply setup() function that creates everything needed for each test case every time.

expect(actual).toMatchInlineSnapshot(`
Object {
"exists": Object {
"field": "test",
},
"meta": Object {
"alias": null,
"disabled": false,
"index": undefined,
"negate": false,
},
"type": "kibana_filter",
}
`);
});
});
65 changes: 65 additions & 0 deletions src/plugins/data/common/search/expressions/exists_filter.ts
Original file line number Diff line number Diff line change
@@ -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',
ppisljar marked this conversation as resolved.
Show resolved Hide resolved
}),
},
},

fn(input, args) {
return {
type: 'kibana_filter',
...buildFilter(
({} as any) as IndexPattern,
args.field.spec,
FILTERS.EXISTS,
args.negate || false,
false,
{},
null
),
};
},
};
42 changes: 42 additions & 0 deletions src/plugins/data/common/search/expressions/field.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 67 additions & 0 deletions src/plugins/data/common/search/expressions/field.ts
Original file line number Diff line number Diff line change
@@ -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 kibana_field',
ppisljar marked this conversation as resolved.
Show resolved Hide resolved
}),
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: 'script in case the field is scripted',
ppisljar marked this conversation as resolved.
Show resolved Hide resolved
}),
},
},

fn(input, args) {
return {
type: 'kibana_field',
spec: {
name: args.name,
type: args.type,
scripted: args.script ? true : false,
script: args.script,
},
} as KibanaField;
},
};
45 changes: 45 additions & 0 deletions src/plugins/data/common/search/expressions/filters_to_ast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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('returns an object with the correct structure', () => {
ppisljar marked this conversation as resolved.
Show resolved Hide resolved
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 {
"query": Array [
Object {
"test": "something",
},
],
}
`);
expect(actual[1].functions[0]).toHaveProperty('name', 'kibanaFilter');
expect(actual[1].functions[0].arguments).toMatchInlineSnapshot(`
Object {
"query": Array [
Object {
"test": "something",
},
],
}
`);
});
});
21 changes: 21 additions & 0 deletions src/plugins/data/common/search/expressions/filters_to_ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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) => {
return buildExpression([
buildExpressionFunction<ExpressionFunctionKibanaFilter>('kibanaFilter', {
query: filter.query,
}),
]);
});
};
6 changes: 6 additions & 0 deletions src/plugins/data/common/search/expressions/index.ts
Original file line number Diff line number Diff line change
@@ -15,4 +15,10 @@ 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 './filters_to_ast';
export * from './timerange';
10 changes: 5 additions & 5 deletions src/plugins/data/common/search/expressions/kibana_context.ts
Original file line number Diff line number Diff line change
@@ -11,13 +11,13 @@ import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, ExecutionContext } from 'src/plugins/expressions/common';
import { Adapters } from 'src/plugins/inspector/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 +56,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 +81,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 || [])];

if (args.savedSearchId) {
if (typeof getSavedObject !== 'function') {
Original file line number Diff line number Diff line change
@@ -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';
Loading