From 92500635844bd48559334a84426c8bc0a5ea89bb Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Sun, 1 Nov 2020 22:25:42 +0300 Subject: [PATCH 1/2] Enable send to background in Timelion --- .../vis_type_timelion/public/helpers/plugin_services.ts | 6 ++++-- .../public/helpers/timelion_request_handler.ts | 3 +++ src/plugins/vis_type_timelion/public/plugin.ts | 3 ++- src/plugins/vis_type_timelion/server/routes/run.ts | 1 + .../vis_type_timelion/server/series_functions/es/index.js | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts b/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts index b055626934eea..6a549ac6f8e0a 100644 --- a/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts +++ b/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts @@ -17,14 +17,16 @@ * under the License. */ -import { IndexPatternsContract } from 'src/plugins/data/public'; -import { SavedObjectsClientContract } from 'kibana/public'; +import type { IndexPatternsContract, ISearchStart } from 'src/plugins/data/public'; +import type { SavedObjectsClientContract } from 'kibana/public'; import { createGetterSetter } from '../../../kibana_utils/public'; export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( 'IndexPatterns' ); +export const [getDataSearch, setDataSearch] = createGetterSetter('Search'); + export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter< SavedObjectsClientContract >('SavedObjectsClient'); diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 975d12a152d89..1f0ac8b2b9392 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -23,6 +23,7 @@ import { TimeRange, Filter, esQuery, Query } from '../../../data/public'; import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; +import { getDataSearch } from '../helpers/plugin_services'; interface Stats { cacheCount: number; @@ -93,6 +94,7 @@ export function getTimelionRequestHandler({ // parse the time range client side to make sure it behaves like other charts const timeRangeBounds = timefilter.calculateBounds(timeRange); + const sessionId = getDataSearch().session.getSessionId(); try { return await http.post('/api/timelion/run', { @@ -109,6 +111,7 @@ export function getTimelionRequestHandler({ interval: visParams.interval, timezone, }, + sessionId, }), }); } catch (e) { diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index bb8fb6b298a07..d74c127dce881 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -36,7 +36,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; -import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; +import { setIndexPatterns, setSavedObjectsClient, setDataSearch } from './helpers/plugin_services'; import { ConfigSchema } from '../config'; import { getArgValueSuggestions } from './helpers/arg_value_suggestions'; @@ -104,6 +104,7 @@ export class TimelionVisPlugin public start(core: CoreStart, plugins: TimelionVisStartDependencies) { setIndexPatterns(plugins.data.indexPatterns); setSavedObjectsClient(core.savedObjects.client); + setDataSearch(plugins.data.search); return { getArgValueSuggestions, diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index 19bb5238f9de0..5766705d9873d 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -75,6 +75,7 @@ export function runRoute( to: schema.maybe(schema.string()), }) ), + sessionId: schema.maybe(schema.string()), }), }, }, diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index fc3250f0d4726..dc99449af8e1a 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -129,7 +129,6 @@ export default new Datasource('es', { const esShardTimeout = tlConfig.esShardTimeout; const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); - const deps = (await tlConfig.getStartServices())[1]; const resp = await deps.data.search @@ -137,6 +136,7 @@ export default new Datasource('es', { body, { strategy: ES_SEARCH_STRATEGY, + sessionId: tlConfig.request.body.sessionId, }, tlConfig.context ) From 60cac46e5bcb8841decbe02840e74e6339b06b29 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 2 Nov 2020 00:22:39 +0300 Subject: [PATCH 2/2] add jest --- .../server/series_functions/es/es.test.js | 212 ++++++++++-------- .../server/series_functions/es/index.js | 2 +- 2 files changed, 116 insertions(+), 98 deletions(-) diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 8be3cf5171c65..e10b3f7e438db 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -26,36 +26,39 @@ import createDateAgg from './lib/create_date_agg'; import esResponse from '../fixtures/es_response'; import _ from 'lodash'; -import { expect } from 'chai'; import sinon from 'sinon'; import invoke from '../helpers/invoke_series_fn.js'; import { UI_SETTINGS } from '../../../../data/server'; -function stubRequestAndServer(response, indexPatternSavedObjects = []) { - return { - getStartServices: sinon - .stub() - .returns( - Promise.resolve([ - {}, - { data: { search: { search: () => from(Promise.resolve(response)) } } }, - ]) - ), - savedObjectsClient: { - find: function () { - return Promise.resolve({ - saved_objects: indexPatternSavedObjects, - }); - }, - }, - }; -} - describe('es', () => { let tlConfig; + let dataSearchStub; + let mockResponse; + + beforeEach(() => { + dataSearchStub = { + data: { + search: { search: jest.fn(() => from(Promise.resolve(mockResponse))) }, + }, + }; + }); + + function stubRequestAndServer(response, indexPatternSavedObjects = []) { + mockResponse = response; + return { + getStartServices: sinon.stub().returns(Promise.resolve([{}, dataSearchStub])), + savedObjectsClient: { + find: function () { + return Promise.resolve({ + saved_objects: indexPatternSavedObjects, + }); + }, + }, + }; + } describe('seriesList processor', () => { - it('throws an error then the index is missing', () => { + test('throws an error then the index is missing', () => { tlConfig = stubRequestAndServer({ rawResponse: { _shards: { total: 0 }, @@ -64,14 +67,29 @@ describe('es', () => { return invoke(es, [5], tlConfig) .then(expect.fail) .catch((e) => { - expect(e).to.be.an('error'); + expect(e instanceof Error).toBeTruthy(); }); }); - it('returns a seriesList', () => { + test('should call data search with sessionId', async () => { + tlConfig = { + ...stubRequestAndServer({ rawResponse: esResponse }), + request: { + body: { + sessionId: 1, + }, + }, + }; + + await invoke(es, [5], tlConfig); + + expect(dataSearchStub.data.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1); + }); + + test('returns a seriesList', () => { tlConfig = stubRequestAndServer({ rawResponse: esResponse }); return invoke(es, [5], tlConfig).then((r) => { - expect(r.output.type).to.eql('seriesList'); + expect(r.output.type).toEqual('seriesList'); }); }); }); @@ -89,51 +107,51 @@ describe('es', () => { agg = createDateAgg(config, tlConfig); }); - it('creates a date_histogram with meta.type of time_buckets', () => { - expect(agg.time_buckets.meta.type).to.eql('time_buckets'); - expect(agg.time_buckets.date_histogram).to.be.an('object'); + test('creates a date_histogram with meta.type of time_buckets', () => { + expect(agg.time_buckets.meta.type).toEqual('time_buckets'); + expect(typeof agg.time_buckets.date_histogram).toBe('object'); }); - it('has extended_bounds that match tlConfig', () => { - expect(agg.time_buckets.date_histogram.extended_bounds.min).to.equal(tlConfig.time.from); - expect(agg.time_buckets.date_histogram.extended_bounds.max).to.equal(tlConfig.time.to); + test('has extended_bounds that match tlConfig', () => { + expect(agg.time_buckets.date_histogram.extended_bounds.min).toEqual(tlConfig.time.from); + expect(agg.time_buckets.date_histogram.extended_bounds.max).toEqual(tlConfig.time.to); }); - it('sets the timezone', () => { - expect(agg.time_buckets.date_histogram.time_zone).to.equal('Etc/UTC'); + test('sets the timezone', () => { + expect(agg.time_buckets.date_histogram.time_zone).toEqual('Etc/UTC'); }); - it('sets the field', () => { - expect(agg.time_buckets.date_histogram.field).to.equal('@timestamp'); + test('sets the field', () => { + expect(agg.time_buckets.date_histogram.field).toEqual('@timestamp'); }); - it('sets the interval for calendar_interval correctly', () => { - expect(agg.time_buckets.date_histogram).to.have.property('calendar_interval', '1y'); + test('sets the interval for calendar_interval correctly', () => { + expect(agg.time_buckets.date_histogram).toHaveProperty('calendar_interval', '1y'); }); - it('sets the interval for fixed_interval correctly', () => { + test('sets the interval for fixed_interval correctly', () => { const a = createDateAgg({ timefield: '@timestamp', interval: '24h' }, tlConfig); - expect(a.time_buckets.date_histogram).to.have.property('fixed_interval', '24h'); + expect(a.time_buckets.date_histogram).toHaveProperty('fixed_interval', '24h'); }); - it('sets min_doc_count to 0', () => { - expect(agg.time_buckets.date_histogram.min_doc_count).to.equal(0); + test('sets min_doc_count to 0', () => { + expect(agg.time_buckets.date_histogram.min_doc_count).toEqual(0); }); describe('metric aggs', () => { const emptyScriptedFields = []; - it('adds a metric agg for each metric', () => { + test('adds a metric agg for each metric', () => { config.metric = ['sum:beer', 'avg:bytes', 'percentiles:bytes']; agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs['sum(beer)']).to.eql({ sum: { field: 'beer' } }); - expect(agg.time_buckets.aggs['avg(bytes)']).to.eql({ avg: { field: 'bytes' } }); - expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ + 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({ percentiles: { field: 'bytes' }, }); }); - it('adds a scripted metric agg for each scripted metric', () => { + test('adds a scripted metric agg for each scripted metric', () => { config.metric = ['avg:scriptedBytes']; const scriptedFields = [ { @@ -143,7 +161,7 @@ describe('es', () => { }, ]; agg = createDateAgg(config, tlConfig, scriptedFields); - expect(agg.time_buckets.aggs['avg(scriptedBytes)']).to.eql({ + expect(agg.time_buckets.aggs['avg(scriptedBytes)']).toEqual({ avg: { script: { source: 'doc["bytes"].value', @@ -153,11 +171,11 @@ describe('es', () => { }); }); - it('has a special `count` metric that uses a script', () => { + test('has a special `count` metric that uses a script', () => { config.metric = ['count']; agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs.count.bucket_script).to.be.an('object'); - expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).to.eql('_count'); + expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object'); + expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count'); }); }); }); @@ -176,43 +194,43 @@ describe('es', () => { }; }); - it('sets the index on the request', () => { + test('sets the index on the request', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.index).to.equal('beer'); + expect(request.params.index).toEqual('beer'); }); - it('always sets body.size to 0', () => { + test('always sets body.size to 0', () => { const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.size).to.equal(0); + expect(request.params.body.size).toEqual(0); }); - it('creates a filters agg that contains each of the queries passed', () => { + test('creates a filters agg that contains each of the queries passed', () => { config.q = ['foo', 'bar']; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.aggs.q.meta.type).to.equal('split'); + expect(request.params.body.aggs.q.meta.type).toEqual('split'); const filters = request.params.body.aggs.q.filters.filters; - expect(filters.foo.query_string.query).to.eql('foo'); - expect(filters.bar.query_string.query).to.eql('bar'); + expect(filters.foo.query_string.query).toEqual('foo'); + expect(filters.bar.query_string.query).toEqual('bar'); }); describe('timeouts', () => { - it('sets the timeout on the request', () => { + test('sets the timeout on the request', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields, 30000); - expect(request.params.timeout).to.equal('30000ms'); + expect(request.params.timeout).toEqual('30000ms'); }); - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields, 0); - expect(request.params).to.not.have.property('timeout'); + expect(request.params).not.toHaveProperty('timeout'); }); }); @@ -227,20 +245,20 @@ describe('es', () => { sandbox.restore(); }); - it('sets ignore_throttled=true on the request', () => { + 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); - expect(request.params.ignore_throttled).to.equal(true); + expect(request.params.ignore_throttled).toEqual(true); }); - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + 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); - expect(request.params.ignore_throttled).to.equal(false); + expect(request.params.ignore_throttled).toEqual(false); }); }); @@ -271,24 +289,24 @@ describe('es', () => { }); }); - it('adds the contents of body.extended.es.filter to a filter clause of the bool', () => { + 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 filter = request.params.body.query.bool.filter.bool; - expect(filter.must.length).to.eql(1); - expect(filter.must_not.length).to.eql(2); + expect(filter.must.length).toEqual(1); + expect(filter.must_not.length).toEqual(2); }); - it('does not include filters if config.kibana = false', () => { + test('does not include filters if config.kibana = false', () => { config.kibana = false; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.filter).to.eql(undefined); + expect(request.params.body.query.bool.filter).toEqual(undefined); }); - it('adds a time filter to the bool querys must clause', () => { + test('adds a time filter to the bool querys must clause', () => { let request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.must.length).to.eql(1); - expect(request.params.body.query.bool.must[0]).to.eql({ + expect(request.params.body.query.bool.must.length).toEqual(1); + expect(request.params.body.query.bool.must[0]).toEqual({ range: { '@timestamp': { format: 'strict_date_optional_time', @@ -300,27 +318,27 @@ describe('es', () => { config.kibana = true; request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.must.length).to.eql(1); + expect(request.params.body.query.bool.must.length).toEqual(1); }); }); describe('config.split', () => { - it('adds terms aggs, in order, under the filters agg', () => { + test('adds terms aggs, in order, under the filters agg', () => { config.split = ['beer:5', 'wine:10']; const request = fn(config, tlConfig, emptyScriptedFields); const aggs = request.params.body.aggs.q.aggs; - expect(aggs.beer.meta.type).to.eql('split'); - expect(aggs.beer.terms.field).to.eql('beer'); - expect(aggs.beer.terms.size).to.eql(5); + expect(aggs.beer.meta.type).toEqual('split'); + expect(aggs.beer.terms.field).toEqual('beer'); + expect(aggs.beer.terms.size).toEqual(5); - expect(aggs.beer.aggs.wine.meta.type).to.eql('split'); - expect(aggs.beer.aggs.wine.terms.field).to.eql('wine'); - expect(aggs.beer.aggs.wine.terms.size).to.eql(10); + expect(aggs.beer.aggs.wine.meta.type).toEqual('split'); + expect(aggs.beer.aggs.wine.terms.field).toEqual('wine'); + expect(aggs.beer.aggs.wine.terms.size).toEqual(10); }); - it('adds scripted terms aggs, in order, under the filters agg', () => { + test('adds scripted terms aggs, in order, under the filters agg', () => { config.split = ['scriptedBeer:5', 'scriptedWine:10']; const scriptedFields = [ { @@ -338,19 +356,19 @@ describe('es', () => { const aggs = request.params.body.aggs.q.aggs; - expect(aggs.scriptedBeer.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.terms.script).to.eql({ + expect(aggs.scriptedBeer.meta.type).toEqual('split'); + expect(aggs.scriptedBeer.terms.script).toEqual({ source: 'doc["beer"].value', lang: 'painless', }); - expect(aggs.scriptedBeer.terms.size).to.eql(5); + expect(aggs.scriptedBeer.terms.size).toEqual(5); - expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).to.eql({ + expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).toEqual('split'); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).toEqual({ source: 'doc["wine"].value', lang: 'painless', }); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).to.eql(10); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).toEqual(10); }); }); }); @@ -364,14 +382,14 @@ describe('es', () => { describe('timeBucketsToPairs', () => { const fn = aggResponse.timeBucketsToPairs; - it('Should convert a single metric agg', () => { + test('Should convert a single metric agg', () => { const buckets = [ { key: 1000, count: { value: 3 } }, { key: 2000, count: { value: 14 } }, { key: 3000, count: { value: 15 } }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ count: [ [1000, 3], [2000, 14], @@ -380,14 +398,14 @@ describe('es', () => { }); }); - it('Should convert multiple metric aggs', () => { + test('Should convert multiple metric aggs', () => { const buckets = [ { key: 1000, count: { value: 3 }, max: { value: 92 } }, { key: 2000, count: { value: 14 }, max: { value: 65 } }, { key: 3000, count: { value: 15 }, max: { value: 35 } }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ count: [ [1000, 3], [2000, 14], @@ -401,7 +419,7 @@ describe('es', () => { }); }); - it('Should convert percentiles metric aggs', () => { + test('Should convert percentiles metric aggs', () => { const buckets = [ { key: 1000, @@ -417,7 +435,7 @@ describe('es', () => { }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ 'percentiles:50.0': [ [1000, NaN], [2000, 25], @@ -442,8 +460,8 @@ describe('es', () => { }); }); - it('should throw an error', () => { - expect(aggResponse.default(esResponse.aggregations, config)).to.eql([ + test('should throw an error', () => { + expect(aggResponse.default(esResponse.aggregations, config)).toEqual([ { data: [ [1000, 264], diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index dc99449af8e1a..75ca1d74f0bf8 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -136,7 +136,7 @@ export default new Datasource('es', { body, { strategy: ES_SEARCH_STRATEGY, - sessionId: tlConfig.request.body.sessionId, + sessionId: tlConfig.request?.body.sessionId, }, tlConfig.context )