[Timelion] Support of Runtime Fields (#96700)

* [Timelion] Support of Runtime Fields

* Replace call of getScriptedFields() with getComputedFields().runtimeFields, refactor buildAggBody and es.test.js

* Refactor index.js and agg_body.js

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Diana Derevyankina 2021-04-29 10:38:57 +03:00 committed by GitHub
parent 71145a6778
commit fe48ae396b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 72 deletions

View file

@ -10,13 +10,7 @@ import { get } from 'lodash';
import { getIndexPatterns } from './plugin_services';
import { TimelionFunctionArgs } from '../../common/types';
import { TimelionExpressionFunction, TimelionExpressionArgument } from '../../common/parser';
import {
IndexPatternField,
indexPatterns as indexPatternsUtils,
KBN_FIELD_TYPES,
} from '../../../data/public';
const isRuntimeField = (field: IndexPatternField) => Boolean(field.runtimeField);
import { indexPatterns as indexPatternsUtils, KBN_FIELD_TYPES } from '../../../data/public';
export function getArgValueSuggestions() {
const indexPatterns = getIndexPatterns();
@ -77,7 +71,6 @@ export function getArgValueSuggestions() {
.getByType(KBN_FIELD_TYPES.NUMBER)
.filter(
(field) =>
!isRuntimeField(field) &&
field.aggregatable &&
containsFieldName(valueSplit[1], field) &&
!indexPatternsUtils.isNestedField(field)
@ -101,7 +94,6 @@ export function getArgValueSuggestions() {
.getAll()
.filter(
(field) =>
!isRuntimeField(field) &&
field.aggregatable &&
[
KBN_FIELD_TYPES.NUMBER,
@ -124,10 +116,7 @@ export function getArgValueSuggestions() {
return indexPattern.fields
.getByType(KBN_FIELD_TYPES.DATE)
.filter(
(field) =>
!isRuntimeField(field) &&
containsFieldName(partial, field) &&
!indexPatternsUtils.isNestedField(field)
(field) => containsFieldName(partial, field) && !indexPatternsUtils.isNestedField(field)
)
.map((field) => ({ name: field.name, insertText: field.name }));
},

View file

@ -120,7 +120,7 @@ describe('es', () => {
});
describe('metric aggs', () => {
const emptyScriptedFields = [];
const emptyScriptFields = {};
test('adds a metric agg for each metric', () => {
config.metric = [
@ -133,7 +133,7 @@ describe('es', () => {
'percentiles:\\:bytes\\:123:20.0,50.0,100.0',
'percentiles:a:2',
];
agg = createDateAgg(config, tlConfig, emptyScriptedFields);
agg = createDateAgg(config, tlConfig, emptyScriptFields);
expect(agg.time_buckets.aggs['sum(beer)']).toEqual({ sum: { field: 'beer' } });
expect(agg.time_buckets.aggs['avg(bytes)']).toEqual({ avg: { field: 'bytes' } });
expect(agg.time_buckets.aggs['percentiles(bytes)']).toEqual({
@ -156,14 +156,15 @@ describe('es', () => {
test('adds a scripted metric agg for each scripted metric', () => {
config.metric = ['avg:scriptedBytes'];
const scriptedFields = [
{
name: 'scriptedBytes',
script: 'doc["bytes"].value',
lang: 'painless',
const scriptFields = {
scriptedBytes: {
script: {
source: 'doc["bytes"].value',
lang: 'painless',
},
},
];
agg = createDateAgg(config, tlConfig, scriptedFields);
};
agg = createDateAgg(config, tlConfig, scriptFields);
expect(agg.time_buckets.aggs['avg(scriptedBytes)']).toEqual({
avg: {
script: {
@ -176,14 +177,14 @@ describe('es', () => {
test('has a special `count` metric that uses a script', () => {
config.metric = ['count'];
agg = createDateAgg(config, tlConfig, emptyScriptedFields);
agg = createDateAgg(config, tlConfig, emptyScriptFields);
expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object');
expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count');
});
test('has a special `count` metric with redundant field which use a script', () => {
config.metric = ['count:beer'];
agg = createDateAgg(config, tlConfig, emptyScriptedFields);
agg = createDateAgg(config, tlConfig, emptyScriptFields);
expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object');
expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count');
});
@ -192,7 +193,7 @@ describe('es', () => {
describe('buildRequest', () => {
const fn = buildRequest;
const emptyScriptedFields = [];
const emptyScriptFields = {};
let tlConfig;
let config;
beforeEach(() => {
@ -206,20 +207,20 @@ describe('es', () => {
test('sets the index on the request', () => {
config.index = 'beer';
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.index).toEqual('beer');
});
test('always sets body.size to 0', () => {
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.body.size).toEqual(0);
});
test('creates a filters agg that contains each of the queries passed', () => {
config.q = ['foo', 'bar'];
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.body.aggs.q.meta.type).toEqual('split');
@ -231,14 +232,14 @@ describe('es', () => {
describe('timeouts', () => {
test('sets the timeout on the request', () => {
config.index = 'beer';
const request = fn(config, tlConfig, emptyScriptedFields, 30000);
const request = fn(config, tlConfig, emptyScriptFields, {}, 30000);
expect(request.params.timeout).toEqual('30000ms');
});
test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => {
config.index = 'beer';
const request = fn(config, tlConfig, emptyScriptedFields, 0);
const request = fn(config, tlConfig, emptyScriptFields, {}, 0);
expect(request.params).not.toHaveProperty('timeout');
});
@ -258,7 +259,7 @@ describe('es', () => {
test('sets ignore_throttled=true on the request', () => {
config.index = 'beer';
tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = false;
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.ignore_throttled).toEqual(true);
});
@ -266,7 +267,7 @@ describe('es', () => {
test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => {
tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = true;
config.index = 'beer';
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.ignore_throttled).toEqual(false);
});
@ -301,7 +302,7 @@ describe('es', () => {
test('adds the contents of body.extended.es.filter to a filter clause of the bool', () => {
config.kibana = true;
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
const filter = request.params.body.query.bool.filter.bool;
expect(filter.must.length).toEqual(1);
expect(filter.must_not.length).toEqual(2);
@ -309,12 +310,12 @@ describe('es', () => {
test('does not include filters if config.kibana = false', () => {
config.kibana = false;
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.body.query.bool.filter).toEqual(undefined);
});
test('adds a time filter to the bool querys must clause', () => {
let request = fn(config, tlConfig, emptyScriptedFields);
let request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.body.query.bool.must.length).toEqual(1);
expect(request.params.body.query.bool.must[0]).toEqual({
range: {
@ -327,7 +328,7 @@ describe('es', () => {
});
config.kibana = true;
request = fn(config, tlConfig, emptyScriptedFields);
request = fn(config, tlConfig, emptyScriptFields);
expect(request.params.body.query.bool.must.length).toEqual(1);
});
});
@ -335,7 +336,7 @@ describe('es', () => {
describe('config.split', () => {
test('adds terms aggs, in order, under the filters agg', () => {
config.split = ['beer:5', 'wine:10', ':lemo:nade::15', ':jui:ce:723::45'];
const request = fn(config, tlConfig, emptyScriptedFields);
const request = fn(config, tlConfig, {});
let aggs = request.params.body.aggs.q.aggs;
@ -362,19 +363,21 @@ describe('es', () => {
test('adds scripted terms aggs, in order, under the filters agg', () => {
config.split = ['scriptedBeer:5', 'scriptedWine:10'];
const scriptedFields = [
{
name: 'scriptedBeer',
script: 'doc["beer"].value',
lang: 'painless',
const scriptFields = {
scriptedBeer: {
script: {
source: 'doc["beer"].value',
lang: 'painless',
},
},
{
name: 'scriptedWine',
script: 'doc["wine"].value',
lang: 'painless',
scriptedWine: {
script: {
source: 'doc["wine"].value',
lang: 'painless',
},
},
];
const request = fn(config, tlConfig, scriptedFields);
};
const request = fn(config, tlConfig, scriptFields);
const aggs = request.params.body.aggs.q.aggs;

View file

@ -101,11 +101,10 @@ export default new Datasource('es', {
(index) => index.title === config.index
);
const scriptedFields = indexPatternSpec?.getScriptedFields() ?? [];
const { scriptFields = {}, runtimeFields = {} } = indexPatternSpec?.getComputedFields() ?? {};
const esShardTimeout = tlConfig.esShardTimeout;
const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout);
const body = buildRequest(config, tlConfig, scriptFields, runtimeFields, esShardTimeout);
const resp = await tlConfig.context.search
.search(

View file

@ -6,21 +6,7 @@
* Side Public License, v 1.
*/
export function buildAggBody(fieldName, scriptedFields) {
const scriptedField = scriptedFields.find((field) => {
return field.name === fieldName;
});
if (scriptedField) {
return {
script: {
source: scriptedField.script,
lang: scriptedField.lang,
},
};
}
return {
export const buildAggBody = (fieldName, scriptFields) =>
scriptFields[fieldName] ?? {
field: fieldName,
};
}

View file

@ -12,7 +12,7 @@ import { buildAggBody } from './agg_body';
import createDateAgg from './create_date_agg';
import { UI_SETTINGS } from '../../../../../data/server';
export default function buildRequest(config, tlConfig, scriptedFields, timeout) {
export default function buildRequest(config, tlConfig, scriptFields, runtimeFields, timeout) {
const bool = { must: [] };
const timeFilter = {
@ -51,7 +51,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout)
(config.split || []).forEach((clause) => {
const [field, arg] = clause.split(/:(\d+$)/);
if (field && arg) {
const termsAgg = buildAggBody(field, scriptedFields);
const termsAgg = buildAggBody(field, scriptFields);
termsAgg.size = parseInt(arg, 10);
aggCursor[field] = {
meta: { type: 'split' },
@ -64,7 +64,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout)
}
});
_.assign(aggCursor, createDateAgg(config, tlConfig, scriptedFields));
_.assign(aggCursor, createDateAgg(config, tlConfig, scriptFields));
const request = {
index: config.index,
@ -75,6 +75,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout)
},
aggs: aggs,
size: 0,
runtime_mappings: runtimeFields,
},
};

View file

@ -11,7 +11,7 @@ import { search, METRIC_TYPES } from '../../../../../data/server';
const { dateHistogramInterval } = search.aggs;
export default function createDateAgg(config, tlConfig, scriptedFields) {
export default function createDateAgg(config, tlConfig, scriptFields) {
const dateAgg = {
time_buckets: {
meta: { type: 'time_buckets' },
@ -47,7 +47,7 @@ export default function createDateAgg(config, tlConfig, scriptedFields) {
const percentArgs = splittedArgs[1];
const metricKey = metricName + '(' + field + ')';
metricBody[metricKey] = { [metricName]: buildAggBody(field, scriptedFields) };
metricBody[metricKey] = { [metricName]: buildAggBody(field, scriptFields) };
if (metricName === METRIC_TYPES.PERCENTILES && percentArgs) {
let percentList = percentArgs.split(',');