[Search] Expose data.search.asyncSearch.* in kibana.yml (#142976)

This commit is contained in:
Anton Dosov 2022-10-11 13:17:46 +02:00 committed by GitHub
parent 87e57135bb
commit 782e5de644
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 529 additions and 251 deletions

View file

@ -41,6 +41,9 @@ kibana_vars=(
csp.report_to
data.autocomplete.valueSuggestions.terminateAfter
data.autocomplete.valueSuggestions.timeout
data.search.asyncSearch.waitForCompletion
data.search.asyncSearch.keepAlive
data.search.asyncSearch.batchedReduceSize
data.search.sessions.defaultExpiration
data.search.sessions.enabled
data.search.sessions.maxUpdateRetries

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 moment from 'moment/moment';
import { SearchConfigSchema, SearchSessionsConfigSchema } from './config';
export const getMockSearchConfig = ({
sessions: { enabled = true, defaultExpiration = moment.duration(7, 'd') } = {
enabled: true,
defaultExpiration: moment.duration(7, 'd'),
},
asyncSearch: {
waitForCompletion = moment.duration(100, 'ms'),
keepAlive = moment.duration(1, 'm'),
batchedReduceSize = 64,
} = {
waitForCompletion: moment.duration(100, 'ms'),
keepAlive: moment.duration(1, 'm'),
batchedReduceSize: 64,
},
}: Partial<{
sessions: Partial<SearchSessionsConfigSchema>;
asyncSearch: Partial<SearchConfigSchema['asyncSearch']>;
}>): SearchConfigSchema =>
({
asyncSearch: {
waitForCompletion,
keepAlive,
batchedReduceSize,
} as SearchConfigSchema['asyncSearch'],
sessions: {
enabled,
defaultExpiration,
} as SearchSessionsConfigSchema,
} as SearchConfigSchema);

View file

@ -46,20 +46,29 @@ export const searchSessionsConfigSchema = schema.object({
}),
});
export const configSchema = schema.object({
search: schema.object({
aggs: schema.object({
shardDelay: schema.object({
// Whether or not to register the shard_delay (which is only available in snapshot versions
// of Elasticsearch) agg type/expression function to make it available in the UI for either
// functional or manual testing
enabled: schema.boolean({ defaultValue: false }),
}),
}),
sessions: searchSessionsConfigSchema,
export const searchConfigSchema = schema.object({
asyncSearch: schema.object({
waitForCompletion: schema.duration({ defaultValue: '100ms' }),
keepAlive: schema.duration({ defaultValue: '1m' }),
batchedReduceSize: schema.number({ defaultValue: 64 }),
}),
aggs: schema.object({
shardDelay: schema.object({
// Whether or not to register the shard_delay (which is only available in snapshot versions
// of Elasticsearch) agg type/expression function to make it available in the UI for either
// functional or manual testing
enabled: schema.boolean({ defaultValue: false }),
}),
}),
sessions: searchSessionsConfigSchema,
});
export const configSchema = schema.object({
search: searchConfigSchema,
});
export type ConfigSchema = TypeOf<typeof configSchema>;
export type SearchConfigSchema = TypeOf<typeof searchConfigSchema>;
export type SearchSessionsConfigSchema = TypeOf<typeof searchSessionsConfigSchema>;

View file

@ -178,6 +178,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
ENHANCED_ES_SEARCH_STRATEGY,
enhancedEsSearchStrategyProvider(
this.initializerContext.config.legacy.globalConfig$,
this.initializerContext.config.get().search,
this.logger,
usage
)
@ -189,13 +190,20 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
// for example use case
this.searchAsInternalUser = enhancedEsSearchStrategyProvider(
this.initializerContext.config.legacy.globalConfig$,
this.initializerContext.config.get().search,
this.logger,
usage,
true
);
this.registerSearchStrategy(EQL_SEARCH_STRATEGY, eqlSearchStrategyProvider(this.logger));
this.registerSearchStrategy(SQL_SEARCH_STRATEGY, sqlSearchStrategyProvider(this.logger));
this.registerSearchStrategy(
EQL_SEARCH_STRATEGY,
eqlSearchStrategyProvider(this.initializerContext.config.get().search, this.logger)
);
this.registerSearchStrategy(
SQL_SEARCH_STRATEGY,
sqlSearchStrategyProvider(this.initializerContext.config.get().search, this.logger)
);
registerBsearchRoute(
bfetch,

View file

@ -49,7 +49,7 @@ export interface IScopedSearchSessionsClient {
expires: Date
) => Promise<SavedObjectsUpdateResponse<SearchSessionSavedObjectAttributes>>;
status: (sessionId: string) => Promise<SearchSessionStatusResponse>;
getConfig: () => SearchSessionsConfigSchema | null;
getConfig: () => SearchSessionsConfigSchema;
}
export interface ISearchSessionService {

View file

@ -6,42 +6,39 @@
* Side Public License, v 1.
*/
import { getCommonDefaultAsyncSubmitParams, getCommonDefaultAsyncGetParams } from './async_utils';
import { getCommonDefaultAsyncGetParams, getCommonDefaultAsyncSubmitParams } from './async_utils';
import moment from 'moment';
import { SearchSessionsConfigSchema } from '../../../../config';
const getMockSearchSessionsConfig = ({
enabled = true,
defaultExpiration = moment.duration(7, 'd'),
} = {}) =>
({
enabled,
defaultExpiration,
} as SearchSessionsConfigSchema);
import { getMockSearchConfig } from '../../../../config.mock';
describe('request utils', () => {
describe('getCommonDefaultAsyncSubmitParams', () => {
test('Uses short `keep_alive` if no `sessionId` is provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
test('Uses `keep_alive` from asyncSearch config if no `sessionId` is provided', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses short `keep_alive` if sessions enabled but no yet saved', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_alive` from config if sessions enabled and session is saved', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
@ -50,29 +47,33 @@ describe('request utils', () => {
expect(params).toHaveProperty('keep_alive', '259200000ms');
});
test('Uses `keepAlive` of `1m` if disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
test('Uses `keepAlive` from asyncSearch config if sessions disabled', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_on_completion` if enabled', async () => {
const mockConfig = getMockSearchSessionsConfig({});
test('Uses `keep_on_completion` if sessions enabled', async () => {
const mockConfig = getMockSearchConfig({});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_on_completion', true);
});
test('Does not use `keep_on_completion` if disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
test('Does not use `keep_on_completion` if sessions disabled', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getCommonDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
@ -83,36 +84,44 @@ describe('request utils', () => {
describe('getCommonDefaultAsyncGetParams', () => {
test('Uses `wait_for_completion_timeout`', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('wait_for_completion_timeout');
});
test('Uses `keep_alive` if `sessionId` is not provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Has short `keep_alive` if `sessionId` is provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
test('Has `keep_alive` from asyncSearch config if `sessionId` is provided', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' });
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Has `keep_alive` from config if `sessionId` is provided and session is stored', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, {
sessionId: 'foo',
@ -122,9 +131,11 @@ describe('request utils', () => {
});
test("Don't extend keepAlive if search has already been extended", async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, {
sessionId: 'foo',
@ -135,9 +146,11 @@ describe('request utils', () => {
});
test("Don't extend keepAlive if search is being restored", async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, {
sessionId: 'foo',
@ -149,12 +162,68 @@ describe('request utils', () => {
});
test('Uses `keep_alive` if `sessionId` is provided but sessions disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getCommonDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' });
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
});
describe('overrides: force disable sessions', () => {
test('Does not use `keep_on_completion` if sessions disabled through overrides', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncSubmitParams(
mockConfig,
{
sessionId: 'foo',
},
{ disableSearchSessions: true }
);
expect(params).toHaveProperty('keep_on_completion', false);
});
test('Uses `keepAlive` from asyncSearch config if sessions disabled through overrides', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncSubmitParams(
mockConfig,
{
sessionId: 'foo',
},
{ disableSearchSessions: true }
);
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_alive` from asyncSearch config if sessions disabled through overrides and session is saved', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getCommonDefaultAsyncSubmitParams(
mockConfig,
{
sessionId: 'foo',
isStored: true,
},
{ disableSearchSessions: true }
);
expect(params).toHaveProperty('keep_alive', '60000ms');
});
});
});

View file

@ -10,29 +10,35 @@ import {
AsyncSearchSubmitRequest,
AsyncSearchGetRequest,
} from '@elastic/elasticsearch/lib/api/types';
import { SearchSessionsConfigSchema } from '../../../../config';
import { ISearchOptions } from '../../../../common';
import { SearchConfigSchema } from '../../../../config';
/**
@internal
*/
export function getCommonDefaultAsyncSubmitParams(
searchSessionsConfig: SearchSessionsConfigSchema | null,
options: ISearchOptions
config: SearchConfigSchema,
options: ISearchOptions,
/**
* Allows to override some of internal logic (e.g. eql / sql searches don't fully support search sessions yet)
*/
overrides?: {
disableSearchSessions?: true;
}
): Pick<
AsyncSearchSubmitRequest,
'keep_alive' | 'wait_for_completion_timeout' | 'keep_on_completion'
> {
const useSearchSessions = searchSessionsConfig?.enabled && !!options.sessionId;
const useSearchSessions =
config.sessions.enabled && !!options.sessionId && !overrides?.disableSearchSessions;
const keepAlive =
useSearchSessions && options.isStored
? `${searchSessionsConfig!.defaultExpiration.asMilliseconds()}ms`
: '1m';
? `${config.sessions.defaultExpiration.asMilliseconds()}ms`
: `${config.asyncSearch.keepAlive.asMilliseconds()}ms`;
return {
// Wait up to 100ms for the response to return
wait_for_completion_timeout: '100ms',
// Wait up to the timeout for the response to return
wait_for_completion_timeout: `${config.asyncSearch.waitForCompletion.asMilliseconds()}ms`,
// If search sessions are used, store and get an async ID even for short running requests.
keep_on_completion: useSearchSessions,
// The initial keepalive is as defined in defaultExpiration if search sessions are used or 1m otherwise.
@ -44,24 +50,31 @@ export function getCommonDefaultAsyncSubmitParams(
@internal
*/
export function getCommonDefaultAsyncGetParams(
searchSessionsConfig: SearchSessionsConfigSchema | null,
options: ISearchOptions
config: SearchConfigSchema,
options: ISearchOptions,
/**
* Allows to override some of internal logic (e.g. eql / sql searches don't fully support search sessions yet)
*/
overrides?: {
disableSearchSessions?: true;
}
): Pick<AsyncSearchGetRequest, 'keep_alive' | 'wait_for_completion_timeout'> {
const useSearchSessions = searchSessionsConfig?.enabled && !!options.sessionId;
const useSearchSessions =
config.sessions.enabled && !!options.sessionId && !overrides?.disableSearchSessions;
return {
// Wait up to 100ms for the response to return
wait_for_completion_timeout: '100ms',
// Wait up to the timeout for the response to return
wait_for_completion_timeout: `${config.asyncSearch.waitForCompletion.asMilliseconds()}ms`,
...(useSearchSessions && options.isStored
? // Use session's keep_alive if search belongs to a stored session
options.isSearchStored || options.isRestore // if search was already stored and extended, then no need to extend keepAlive
? {}
: {
keep_alive: `${searchSessionsConfig!.defaultExpiration.asMilliseconds()}ms`,
keep_alive: `${config.sessions.defaultExpiration.asMilliseconds()}ms`,
}
: {
// We still need to do polling for searches not within the context of a search session or when search session disabled
keep_alive: '1m',
keep_alive: `${config.asyncSearch.keepAlive.asMilliseconds()}ms`,
}),
};
}

View file

@ -11,6 +11,7 @@ import { eqlSearchStrategyProvider } from './eql_search_strategy';
import { SearchStrategyDependencies } from '../../types';
import { EqlSearchStrategyRequest } from '../../../../common';
import { firstValueFrom } from 'rxjs';
import { getMockSearchConfig } from '../../../../config.mock';
const getMockEqlResponse = () => ({
body: {
@ -32,6 +33,7 @@ const getMockEqlResponse = () => ({
describe('EQL search strategy', () => {
let mockLogger: Logger;
const mockSearchConfig = getMockSearchConfig({});
beforeEach(() => {
mockLogger = { debug: jest.fn() } as unknown as Logger;
@ -39,12 +41,12 @@ describe('EQL search strategy', () => {
describe('strategy interface', () => {
it('returns a strategy with a `search` function', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
expect(typeof eqlSearch.search).toBe('function');
});
it('returns a strategy with a `cancel` function', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
expect(typeof eqlSearch.cancel).toBe('function');
});
});
@ -81,7 +83,7 @@ describe('EQL search strategy', () => {
describe('async functionality', () => {
it('performs an eql client search with params when no ID is provided', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request, requestOptions]] = mockEqlSearch.mock.calls;
@ -89,7 +91,7 @@ describe('EQL search strategy', () => {
body: { query: 'process where 1 == 1' },
ignore_unavailable: true,
index: 'logstash-*',
keep_alive: '1m',
keep_alive: '60000ms',
max_concurrent_shard_requests: undefined,
wait_for_completion_timeout: '100ms',
});
@ -97,14 +99,14 @@ describe('EQL search strategy', () => {
});
it('retrieves the current request if an id is provided', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch.search({ id: 'my-search-id' }, {}, mockDeps).toPromise();
const [[requestParams]] = mockEqlGet.mock.calls;
expect(mockEqlSearch).not.toHaveBeenCalled();
expect(requestParams).toEqual({
id: 'my-search-id',
keep_alive: '1m',
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
});
});
@ -112,7 +114,7 @@ describe('EQL search strategy', () => {
it('emits an error if the client throws', async () => {
expect.assertions(1);
mockEqlSearch.mockReset().mockRejectedValueOnce(new Error('client error'));
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
eqlSearch.search({ options, params }, {}, mockDeps).subscribe(
() => {},
(err) => {
@ -124,7 +126,7 @@ describe('EQL search strategy', () => {
describe('arguments', () => {
it('sends along async search options', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request]] = mockEqlSearch.mock.calls;
@ -136,7 +138,7 @@ describe('EQL search strategy', () => {
});
it('sends along default search parameters', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request]] = mockEqlSearch.mock.calls;
@ -148,7 +150,7 @@ describe('EQL search strategy', () => {
});
it('allows search parameters to be overridden', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch
.search(
{
@ -174,7 +176,7 @@ describe('EQL search strategy', () => {
});
it('allows search options to be overridden', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch
.search(
{
@ -196,7 +198,7 @@ describe('EQL search strategy', () => {
});
it('passes (deprecated) transport options for an existing request', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await eqlSearch
.search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockDeps)
.toPromise();
@ -207,7 +209,7 @@ describe('EQL search strategy', () => {
});
it('passes abort signal', async () => {
const eqlSearch = eqlSearchStrategyProvider(mockLogger);
const eqlSearch = eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
const eql: EqlSearchStrategyRequest = { id: 'my-search-id' };
const abortController = new AbortController();
await firstValueFrom(
@ -219,7 +221,7 @@ describe('EQL search strategy', () => {
});
it('passes transport options for search with id', async () => {
const eqlSearch = eqlSearchStrategyProvider(mockLogger);
const eqlSearch = eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
const eql: EqlSearchStrategyRequest = { id: 'my-search-id' };
await firstValueFrom(
eqlSearch.search(eql, { transport: { maxResponseSize: 13131313 } }, mockDeps)
@ -234,7 +236,7 @@ describe('EQL search strategy', () => {
});
it('passes transport options for search without id', async () => {
const eqlSearch = eqlSearchStrategyProvider(mockLogger);
const eqlSearch = eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
const eql: EqlSearchStrategyRequest = { params: { index: 'all' } };
await firstValueFrom(eqlSearch.search(eql, { transport: { ignore: [400] } }, mockDeps));
const [[_params, requestOptions]] = mockEqlSearch.mock.calls;
@ -245,7 +247,7 @@ describe('EQL search strategy', () => {
describe('response', () => {
it('contains a rawResponse field containing the full search response', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger);
const response = await eqlSearch
.search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockDeps)
.toPromise();

View file

@ -9,6 +9,7 @@
import type { TransportResult } from '@elastic/elasticsearch';
import { tap } from 'rxjs/operators';
import type { IScopedClusterClient, Logger } from '@kbn/core/server';
import { SearchConfigSchema } from '../../../../config';
import {
EqlSearchStrategyRequest,
EqlSearchStrategyResponse,
@ -23,6 +24,7 @@ import { getIgnoreThrottled } from '../ese_search/request_utils';
import { getCommonDefaultAsyncGetParams } from '../common/async_utils';
export const eqlSearchStrategyProvider = (
searchConfig: SearchConfigSchema,
logger: Logger
): ISearchStrategy<EqlSearchStrategyRequest, EqlSearchStrategyResponse> => {
async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) {
@ -46,11 +48,15 @@ export const eqlSearchStrategyProvider = (
uiSettingsClient
);
const params = id
? getCommonDefaultAsyncGetParams(null, options)
? getCommonDefaultAsyncGetParams(searchConfig, options, {
/* disable until full eql support */ disableSearchSessions: true,
})
: {
...(await getIgnoreThrottled(uiSettingsClient)),
...defaultParams,
...getCommonDefaultAsyncGetParams(null, options),
...getCommonDefaultAsyncGetParams(searchConfig, options, {
/* disable until full eql support */ disableSearchSessions: true,
}),
...request.params,
};
const response = id

View file

@ -14,6 +14,7 @@ import * as xContentParseException from '../../../../common/search/test_data/x_c
import { SearchStrategyDependencies } from '../../types';
import { enhancedEsSearchStrategyProvider } from './ese_search_strategy';
import { createSearchSessionsClientMock } from '../../mocks';
import { getMockSearchConfig } from '../../../../config.mock';
const mockAsyncResponse = {
body: {
@ -74,6 +75,8 @@ describe('ES search strategy', () => {
},
});
const mockSearchConfig = getMockSearchConfig({});
beforeEach(() => {
mockApiCaller.mockClear();
mockGetCaller.mockClear();
@ -82,7 +85,11 @@ describe('ES search strategy', () => {
});
it('returns a strategy with `search and `cancel`', async () => {
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
expect(typeof esSearch.search).toBe('function');
});
@ -93,7 +100,11 @@ describe('ES search strategy', () => {
mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ params }, {}, mockDeps).toPromise();
@ -101,14 +112,18 @@ describe('ES search strategy', () => {
const request = mockSubmitCaller.mock.calls[0][0];
expect(request.index).toEqual(params.index);
expect(request.body).toEqual(params.body);
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
it('makes a GET request to async search with ID', async () => {
mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();
@ -116,14 +131,18 @@ describe('ES search strategy', () => {
const request = mockGetCaller.mock.calls[0][0];
expect(request.id).toEqual('foo');
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
it('sets transport options on POST requests', async () => {
const transportOptions = { maxRetries: 1 };
mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await firstValueFrom(
esSearch.search({ params }, { transport: transportOptions }, mockDeps)
@ -136,7 +155,7 @@ describe('ES search strategy', () => {
body: { query: {} },
ignore_unavailable: true,
index: 'logstash-*',
keep_alive: '1m',
keep_alive: '60000ms',
keep_on_completion: false,
max_concurrent_shard_requests: undefined,
track_total_hits: true,
@ -149,7 +168,11 @@ describe('ES search strategy', () => {
it('sets transport options on GET requests', async () => {
mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await firstValueFrom(
esSearch.search({ id: 'foo', params }, { transport: { maxRetries: 1 } }, mockDeps)
@ -159,7 +182,7 @@ describe('ES search strategy', () => {
1,
expect.objectContaining({
id: 'foo',
keep_alive: '1m',
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
}),
expect.objectContaining({ maxRetries: 1, meta: true, signal: undefined })
@ -170,7 +193,11 @@ describe('ES search strategy', () => {
mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'foo-*', body: {} };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ params }, {}, mockDeps).toPromise();
@ -184,7 +211,11 @@ describe('ES search strategy', () => {
mockApiCaller.mockResolvedValueOnce(mockRollupResponse);
const params = { index: 'foo-程', body: {} };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch
.search(
@ -209,7 +240,11 @@ describe('ES search strategy', () => {
mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ params }, { sessionId: '1' }, mockDeps).toPromise();
@ -218,14 +253,18 @@ describe('ES search strategy', () => {
expect(request.index).toEqual(params.index);
expect(request.body).toEqual(params.body);
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
it('Submit search with session id and session is saved creates a search with long keep_alive', async () => {
mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ params }, { sessionId: '1', isStored: true }, mockDeps).toPromise();
@ -241,7 +280,11 @@ describe('ES search strategy', () => {
mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.search({ id: 'foo', params }, { sessionId: '1' }, mockDeps).toPromise();
@ -249,14 +292,18 @@ describe('ES search strategy', () => {
const request = mockGetCaller.mock.calls[0][0];
expect(request.id).toEqual('foo');
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
it('makes a GET request to async search with long keepalive, if session is saved', async () => {
mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch
.search({ id: 'foo', params }, { sessionId: '1', isStored: true }, mockDeps)
@ -273,7 +320,11 @@ describe('ES search strategy', () => {
mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch
.search(
@ -303,7 +354,11 @@ describe('ES search strategy', () => {
mockSubmitCaller.mockRejectedValue(errResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
let err: KbnServerError | undefined;
try {
@ -324,7 +379,11 @@ describe('ES search strategy', () => {
mockSubmitCaller.mockRejectedValue(errResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
let err: KbnServerError | undefined;
try {
@ -345,7 +404,11 @@ describe('ES search strategy', () => {
mockDeleteCaller.mockResolvedValueOnce(200);
const id = 'some_id';
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.cancel!(id, {}, mockDeps);
@ -365,7 +428,11 @@ describe('ES search strategy', () => {
mockDeleteCaller.mockRejectedValue(errResponse);
const id = 'some_id';
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
let err: KbnServerError | undefined;
try {
@ -388,7 +455,11 @@ describe('ES search strategy', () => {
const id = 'some_other_id';
const keepAlive = '1d';
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
await esSearch.extend!(id, keepAlive, {}, mockDeps);
@ -403,7 +474,11 @@ describe('ES search strategy', () => {
const id = 'some_other_id';
const keepAlive = '1d';
const esSearch = await enhancedEsSearchStrategyProvider(mockLegacyConfig$, mockLogger);
const esSearch = await enhancedEsSearchStrategyProvider(
mockLegacyConfig$,
mockSearchConfig,
mockLogger
);
let err: KbnServerError | undefined;
try {

View file

@ -33,9 +33,11 @@ import {
getTotalLoaded,
shimHitsTotal,
} from '../es_search';
import { SearchConfigSchema } from '../../../../config';
export const enhancedEsSearchStrategyProvider = (
legacyConfig$: Observable<SharedGlobalConfig>,
searchConfig: SearchConfigSchema,
logger: Logger,
usage?: SearchUsage,
useInternalUser: boolean = false
@ -52,19 +54,15 @@ export const enhancedEsSearchStrategyProvider = (
function asyncSearch(
{ id, ...request }: IEsSearchRequest,
options: IAsyncSearchOptions,
{ esClient, uiSettingsClient, searchSessionsClient }: SearchStrategyDependencies
{ esClient, uiSettingsClient }: SearchStrategyDependencies
) {
const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser;
const search = async () => {
const params = id
? getDefaultAsyncGetParams(searchSessionsClient.getConfig(), options)
? getDefaultAsyncGetParams(searchConfig, options)
: {
...(await getDefaultAsyncSubmitParams(
uiSettingsClient,
searchSessionsClient.getConfig(),
options
)),
...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)),
...request.params,
};
const { body, headers } = id

View file

@ -14,21 +14,12 @@ import {
import { IUiSettingsClient } from '@kbn/core/server';
import { UI_SETTINGS } from '../../../../common';
import moment from 'moment';
import { SearchSessionsConfigSchema } from '../../../../config';
import { getMockSearchConfig } from '../../../../config.mock';
const getMockUiSettingsClient = (config: Record<string, unknown>) => {
return { get: async (key: string) => config[key] } as IUiSettingsClient;
};
const getMockSearchSessionsConfig = ({
enabled = true,
defaultExpiration = moment.duration(7, 'd'),
} = {}) =>
({
enabled,
defaultExpiration,
} as SearchSessionsConfigSchema);
describe('request utils', () => {
describe('getIgnoreThrottled', () => {
test('does not return `ignore_throttled` when `includeFrozen` is `false`', async () => {
@ -53,19 +44,23 @@ describe('request utils', () => {
const mockUiSettingsClient = getMockUiSettingsClient({
[UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
});
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_alive` from config if enabled and session is stored', async () => {
const mockUiSettingsClient = getMockUiSettingsClient({
[UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
});
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, {
sessionId: 'foo',
@ -78,21 +73,23 @@ describe('request utils', () => {
const mockUiSettingsClient = getMockUiSettingsClient({
[UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
});
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_on_completion` if enabled', async () => {
const mockUiSettingsClient = getMockUiSettingsClient({
[UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
});
const mockConfig = getMockSearchSessionsConfig({});
const mockConfig = getMockSearchConfig({});
const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, {
sessionId: 'foo',
});
@ -103,9 +100,11 @@ describe('request utils', () => {
const mockUiSettingsClient = getMockUiSettingsClient({
[UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: false,
});
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = await getDefaultAsyncSubmitParams(mockUiSettingsClient, mockConfig, {
sessionId: 'foo',
@ -116,27 +115,33 @@ describe('request utils', () => {
describe('getDefaultAsyncGetParams', () => {
test('Uses `wait_for_completion_timeout`', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('wait_for_completion_timeout');
});
test('Uses `keep_alive` if `sessionId` is not provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Has no `keep_alive` if `sessionId` is provided and search already stored', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {
sessionId: 'foo',
@ -147,12 +152,14 @@ describe('request utils', () => {
});
test('Uses `keep_alive` if `sessionId` is provided but sessions disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' });
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
});
});

View file

@ -11,7 +11,7 @@ import { AsyncSearchGetRequest } from '@elastic/elasticsearch/lib/api/typesWithB
import { AsyncSearchSubmitRequest } from '@elastic/elasticsearch/lib/api/types';
import { ISearchOptions, UI_SETTINGS } from '../../../../common';
import { getDefaultSearchParams } from '../es_search';
import { SearchSessionsConfigSchema } from '../../../../config';
import { SearchConfigSchema } from '../../../../config';
import {
getCommonDefaultAsyncGetParams,
getCommonDefaultAsyncSubmitParams,
@ -32,7 +32,7 @@ export async function getIgnoreThrottled(
*/
export async function getDefaultAsyncSubmitParams(
uiSettingsClient: Pick<IUiSettingsClient, 'get'>,
searchSessionsConfig: SearchSessionsConfigSchema | null,
searchConfig: SearchConfigSchema,
options: ISearchOptions
): Promise<
Pick<
@ -49,11 +49,10 @@ export async function getDefaultAsyncSubmitParams(
> {
return {
// TODO: adjust for partial results
batched_reduce_size: 64,
...getCommonDefaultAsyncSubmitParams(searchSessionsConfig, options),
batched_reduce_size: searchConfig.asyncSearch.batchedReduceSize,
...getCommonDefaultAsyncSubmitParams(searchConfig, options),
...(await getIgnoreThrottled(uiSettingsClient)),
...(await getDefaultSearchParams(uiSettingsClient)),
// If search sessions are used, set the initial expiration time.
};
}
@ -61,10 +60,10 @@ export async function getDefaultAsyncSubmitParams(
@internal
*/
export function getDefaultAsyncGetParams(
searchSessionsConfig: SearchSessionsConfigSchema | null,
searchConfig: SearchConfigSchema,
options: ISearchOptions
): Pick<AsyncSearchGetRequest, 'keep_alive' | 'wait_for_completion_timeout'> {
return {
...getCommonDefaultAsyncGetParams(searchSessionsConfig, options),
...getCommonDefaultAsyncGetParams(searchConfig, options),
};
}

View file

@ -8,30 +8,26 @@
import { getDefaultAsyncSubmitParams, getDefaultAsyncGetParams } from './request_utils';
import moment from 'moment';
import { SearchSessionsConfigSchema } from '../../../../config';
const getMockSearchSessionsConfig = ({
enabled = true,
defaultExpiration = moment.duration(7, 'd'),
} = {}) =>
({
enabled,
defaultExpiration,
} as SearchSessionsConfigSchema);
import { getMockSearchConfig } from '../../../../config.mock';
describe('request utils', () => {
describe('getDefaultAsyncSubmitParams', () => {
test('Uses `keep_alive` from default params if no `sessionId` is provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getDefaultAsyncSubmitParams(mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_alive` from config if enabled and session is stored', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
// unskip when SQL has full session support https://github.com/elastic/kibana/issues/127880
test.skip('Uses `keep_alive` from config if enabled and session is stored', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
@ -40,29 +36,57 @@ describe('request utils', () => {
expect(params).toHaveProperty('keep_alive', '259200000ms');
});
// remove when SQL has full session support https://github.com/elastic/kibana/issues/127880
test('Uses `keep_alive` from asyncSearch config if sessions enabled and session is stored', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
},
});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
isStored: true,
});
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keepAlive` of `1m` if disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Uses `keep_on_completion` if enabled', async () => {
const mockConfig = getMockSearchSessionsConfig({});
// unskip when SQL has full session support https://github.com/elastic/kibana/issues/127880
test.skip('Uses `keep_on_completion` if sessions enabled', async () => {
const mockConfig = getMockSearchConfig({});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_on_completion', true);
});
// remove when SQL has full session support https://github.com/elastic/kibana/issues/127880
test("Don't use `keep_on_completion` if sessions enabled", async () => {
const mockConfig = getMockSearchConfig({});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
});
expect(params).toHaveProperty('keep_on_completion', false);
});
test('Does not use `keep_on_completion` if disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getDefaultAsyncSubmitParams(mockConfig, {
sessionId: 'foo',
@ -73,27 +97,34 @@ describe('request utils', () => {
describe('getDefaultAsyncGetParams', () => {
test('Uses `wait_for_completion_timeout`', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('wait_for_completion_timeout');
});
test('Uses `keep_alive` if `sessionId` is not provided', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {});
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
test('Has no `keep_alive` if `sessionId` is provided, search and session are stored', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
// remove when SQL has full session support https://github.com/elastic/kibana/issues/127880
test.skip('Has no `keep_alive` if `sessionId` is provided, search and session are stored', async () => {
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: true,
},
});
const params = getDefaultAsyncGetParams(mockConfig, {
sessionId: 'foo',
@ -104,12 +135,14 @@ describe('request utils', () => {
});
test('Uses `keep_alive` if `sessionId` is provided but sessions disabled', async () => {
const mockConfig = getMockSearchSessionsConfig({
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
const mockConfig = getMockSearchConfig({
sessions: {
defaultExpiration: moment.duration(3, 'd'),
enabled: false,
},
});
const params = getDefaultAsyncGetParams(mockConfig, { sessionId: 'foo' });
expect(params).toHaveProperty('keep_alive', '1m');
expect(params).toHaveProperty('keep_alive', '60000ms');
});
});
});

View file

@ -8,7 +8,7 @@
import { SqlGetAsyncRequest, SqlQueryRequest } from '@elastic/elasticsearch/lib/api/types';
import { ISearchOptions } from '../../../../common';
import { SearchSessionsConfigSchema } from '../../../../config';
import { SearchConfigSchema } from '../../../../config';
import {
getCommonDefaultAsyncGetParams,
getCommonDefaultAsyncSubmitParams,
@ -18,11 +18,17 @@ import {
@internal
*/
export function getDefaultAsyncSubmitParams(
searchSessionsConfig: SearchSessionsConfigSchema | null,
searchConfig: SearchConfigSchema,
options: ISearchOptions
): Pick<SqlQueryRequest, 'keep_alive' | 'wait_for_completion_timeout' | 'keep_on_completion'> {
return {
...getCommonDefaultAsyncSubmitParams(searchSessionsConfig, options),
...getCommonDefaultAsyncSubmitParams(searchConfig, options, {
/**
* force disable search sessions until sessions support SQL
* https://github.com/elastic/kibana/issues/127880
*/
disableSearchSessions: true,
}),
};
}
@ -30,10 +36,16 @@ export function getDefaultAsyncSubmitParams(
@internal
*/
export function getDefaultAsyncGetParams(
searchSessionsConfig: SearchSessionsConfigSchema | null,
searchConfig: SearchConfigSchema,
options: ISearchOptions
): Pick<SqlGetAsyncRequest, 'keep_alive' | 'wait_for_completion_timeout'> {
return {
...getCommonDefaultAsyncGetParams(searchSessionsConfig, options),
...getCommonDefaultAsyncGetParams(searchConfig, options, {
/**
* force disable search sessions until sessions support SQL
* https://github.com/elastic/kibana/issues/127880
*/
disableSearchSessions: true,
}),
};
}

View file

@ -14,6 +14,7 @@ import { SearchStrategyDependencies } from '../../types';
import { sqlSearchStrategyProvider } from './sql_search_strategy';
import { createSearchSessionsClientMock } from '../../mocks';
import { SqlSearchStrategyRequest } from '../../../../common';
import { getMockSearchConfig } from '../../../../config.mock';
const mockSqlResponse = {
body: {
@ -46,6 +47,8 @@ describe('SQL search strategy', () => {
searchSessionsClient: createSearchSessionsClientMock(),
} as unknown as SearchStrategyDependencies;
const mockSearchConfig = getMockSearchConfig({});
beforeEach(() => {
mockSqlGetAsync.mockClear();
mockSqlQuery.mockClear();
@ -54,7 +57,7 @@ describe('SQL search strategy', () => {
});
it('returns a strategy with `search and `cancel`, `extend`', async () => {
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
expect(typeof esSearch.search).toBe('function');
expect(typeof esSearch.cancel).toBe('function');
@ -70,7 +73,7 @@ describe('SQL search strategy', () => {
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch
.search({ params }, { transport: { requestTimeout: 30000 } }, mockDeps)
@ -80,8 +83,8 @@ describe('SQL search strategy', () => {
const [request, searchOptions] = mockSqlQuery.mock.calls[0];
expect(request).toEqual({
format: 'json',
keep_alive: '1m',
keep_on_completion: undefined,
keep_alive: '60000ms',
keep_on_completion: false,
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
wait_for_completion_timeout: '100ms',
@ -101,7 +104,7 @@ describe('SQL search strategy', () => {
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch
.search({ id: 'foo', params }, { transport: { requestTimeout: 30000 } }, mockDeps)
@ -112,7 +115,7 @@ describe('SQL search strategy', () => {
expect(request).toEqual({
format: 'json',
id: 'foo',
keep_alive: '1m',
keep_alive: '60000ms',
wait_for_completion_timeout: '100ms',
});
expect(searchOptions).toEqual({
@ -131,7 +134,7 @@ describe('SQL search strategy', () => {
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ params }, { sessionId: '1' }, mockDeps).toPromise();
@ -150,7 +153,7 @@ describe('SQL search strategy', () => {
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ id: 'foo', params }, { sessionId: '1' }, mockDeps).toPromise();
@ -169,7 +172,7 @@ describe('SQL search strategy', () => {
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ params }, { sessionId: '1' }, mockDeps).toPromise();
@ -178,7 +181,7 @@ describe('SQL search strategy', () => {
expect(request.query).toEqual(params.query);
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
it('makes a GET request to async search with keepalive', async () => {
@ -189,7 +192,7 @@ describe('SQL search strategy', () => {
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ id: 'foo', params }, { sessionId: '1' }, mockDeps).toPromise();
@ -197,7 +200,7 @@ describe('SQL search strategy', () => {
const request = mockSqlGetAsync.mock.calls[0][0];
expect(request.id).toEqual('foo');
expect(request).toHaveProperty('wait_for_completion_timeout');
expect(request).toHaveProperty('keep_alive', '1m');
expect(request).toHaveProperty('keep_alive', '60000ms');
});
});
@ -216,7 +219,7 @@ describe('SQL search strategy', () => {
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
let err: KbnServerError | undefined;
try {
@ -240,7 +243,7 @@ describe('SQL search strategy', () => {
query:
'SELECT customer_first_name FROM kibana_sample_data_ecommerce ORDER BY order_date DESC',
};
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
let err: KbnServerError | undefined;
try {
@ -262,7 +265,7 @@ describe('SQL search strategy', () => {
})
);
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ id: 'foo', params: { query: 'query' } }, {}, mockDeps).toPromise();
expect(mockSqlClearCursor).not.toHaveBeenCalled();
@ -273,7 +276,7 @@ describe('SQL search strategy', () => {
merge({}, mockSqlResponse, { body: { cursor: 'cursor' } })
);
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch
.search({ id: 'foo', params: { query: 'query', keep_cursor: true } }, {}, mockDeps)
.toPromise();
@ -286,7 +289,7 @@ describe('SQL search strategy', () => {
merge({}, mockSqlResponse, { body: { cursor: 'cursor' } })
);
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.search({ id: 'foo', params: { query: 'query' } }, {}, mockDeps).toPromise();
expect(mockSqlClearCursor).toHaveBeenCalledWith({ cursor: 'cursor' });
@ -295,7 +298,7 @@ describe('SQL search strategy', () => {
it('returns the time it took to run a search', async () => {
mockSqlGetAsync.mockResolvedValueOnce(mockSqlResponse);
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await expect(
esSearch.search({ id: 'foo', params: { query: 'query' } }, {}, mockDeps).toPromise()
).resolves.toHaveProperty('took', expect.any(Number));
@ -307,7 +310,7 @@ describe('SQL search strategy', () => {
mockSqlDelete.mockResolvedValueOnce(200);
const id = 'some_id';
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.cancel!(id, {}, mockDeps);
@ -323,7 +326,7 @@ describe('SQL search strategy', () => {
const id = 'some_other_id';
const keepAlive = '1d';
const esSearch = await sqlSearchStrategyProvider(mockLogger);
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
await esSearch.extend!(id, keepAlive, {}, mockDeps);
expect(mockSqlGetAsync).toBeCalled();

View file

@ -20,8 +20,10 @@ import type {
import { pollSearch } from '../../../../common';
import { getDefaultAsyncGetParams, getDefaultAsyncSubmitParams } from './request_utils';
import { toAsyncKibanaSearchResponse } from './response_utils';
import { SearchConfigSchema } from '../../../../config';
export const sqlSearchStrategyProvider = (
searchConfig: SearchConfigSchema,
logger: Logger,
useInternalUser: boolean = false
): ISearchStrategy<SqlSearchStrategyRequest, SqlSearchStrategyResponse> => {
@ -42,11 +44,6 @@ export const sqlSearchStrategyProvider = (
const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser;
const startTime = Date.now();
// disable search sessions until session task manager supports SQL
// https://github.com/elastic/kibana/issues/127880
// const sessionConfig = searchSessionsClient.getConfig();
const sessionConfig = null;
const search = async () => {
const { keep_cursor: keepCursor, ...params } = request.params ?? {};
let body: SqlQueryResponse;
@ -56,7 +53,7 @@ export const sqlSearchStrategyProvider = (
({ body, headers } = await client.sql.getAsync(
{
format: params?.format ?? 'json',
...getDefaultAsyncGetParams(sessionConfig, options),
...getDefaultAsyncGetParams(searchConfig, options),
id,
},
{ ...options.transport, signal: options.abortSignal, meta: true }
@ -65,7 +62,7 @@ export const sqlSearchStrategyProvider = (
({ headers, body } = await client.sql.query(
{
format: params.format ?? 'json',
...getDefaultAsyncSubmitParams(sessionConfig, options),
...getDefaultAsyncSubmitParams(searchConfig, options),
...params,
},
{ ...options.transport, signal: options.abortSignal, meta: true }

View file

@ -11,6 +11,7 @@
"public/**/*",
"server/**/*",
"config.ts",
"config.mock.ts",
"common/**/*.json",
"public/**/*.json",
"../../../typings/index.d.ts"

View file

@ -91,6 +91,9 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'unifiedSearch.autocomplete.valueSuggestions.tiers (array)',
'unifiedSearch.autocomplete.valueSuggestions.timeout (duration)',
'data.search.aggs.shardDelay.enabled (boolean)',
'data.search.asyncSearch.batchedReduceSize (number)',
'data.search.asyncSearch.keepAlive (duration)',
'data.search.asyncSearch.waitForCompletion (duration)',
'data.search.sessions.defaultExpiration (duration)',
'data.search.sessions.enabled (boolean)',
'data.search.sessions.management.expiresSoonWarning (duration)',