[data.search] Expose SearchSource on the server. (#78383) (#78891)

# Conflicts:
#	src/plugins/data/server/server.api.md
This commit is contained in:
Luke Elmers 2020-09-29 19:23:53 -06:00 committed by GitHub
parent 93145aba3f
commit a95cdebbba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 932 additions and 344 deletions

View file

@ -17,4 +17,5 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
| [aggs](./kibana-plugin-plugins-data-server.isearchstart.aggs.md) | <code>AggsStart</code> | |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | <code>(name: string) =&gt; ISearchStrategy&lt;SearchStrategyRequest, SearchStrategyResponse&gt;</code> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | <code>(context: RequestHandlerContext, request: SearchStrategyRequest, options: ISearchOptions) =&gt; Promise&lt;SearchStrategyResponse&gt;</code> | |
| [searchSource](./kibana-plugin-plugins-data-server.isearchstart.searchsource.md) | <code>{</code><br/><code> asScoped: (request: KibanaRequest) =&gt; Promise&lt;ISearchStartSearchSource&gt;;</code><br/><code> }</code> | |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) &gt; [searchSource](./kibana-plugin-plugins-data-server.isearchstart.searchsource.md)
## ISearchStart.searchSource property
<b>Signature:</b>
```typescript
searchSource: {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
};
```

View file

@ -8,13 +8,13 @@
```typescript
start(core: CoreStart): {
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest<unknown, unknown, unknown, any>) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
```
@ -27,12 +27,12 @@ start(core: CoreStart): {
<b>Returns:</b>
`{
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest<unknown, unknown, unknown, any>) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
}`

View file

@ -17,11 +17,12 @@
* under the License.
*/
export { SearchSource, ISearchSource, SearchSourceDependencies } from './search_source';
export { createSearchSource } from './create_search_source';
export { SortDirection, EsQuerySortValue, SearchSourceFields } from './types';
export { injectReferences } from './inject_references';
export { extractReferences } from './extract_references';
export { parseSearchSourceJSON } from './parse_json';
export * from './fetch';
export * from './legacy';
export * from './search_source';
export * from './search_source_service';
export * from './types';

View file

@ -0,0 +1,120 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SavedObjectReference } from 'src/core/types';
import { SearchSourceFields } from './types';
import { injectReferences } from './inject_references';
describe('injectSearchSourceReferences', () => {
let searchSourceJSON: SearchSourceFields & { indexRefName: string };
let references: SavedObjectReference[];
beforeEach(() => {
searchSourceJSON = {
highlightAll: true,
version: true,
query: {
query: 'play_name:"Henry IV"',
language: 'kuery',
},
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
};
references = [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: '033af690-fde7-11ea-91f3-fb9e73f9bbe9',
},
];
});
test('injects references', () => {
const actual = injectReferences(searchSourceJSON, references);
expect(actual).toMatchInlineSnapshot(`
Object {
"highlightAll": true,
"index": "033af690-fde7-11ea-91f3-fb9e73f9bbe9",
"query": Object {
"language": "kuery",
"query": "play_name:\\"Henry IV\\"",
},
"version": true,
}
`);
});
test('skips injecting references if none exists', () => {
// @ts-expect-error
delete searchSourceJSON.indexRefName;
references = [];
const actual = injectReferences(searchSourceJSON, references);
expect(actual).toMatchInlineSnapshot(`
Object {
"highlightAll": true,
"query": Object {
"language": "kuery",
"query": "play_name:\\"Henry IV\\"",
},
"version": true,
}
`);
});
test('throws an error if there is a broken reference', () => {
searchSourceJSON.indexRefName = 'oops';
expect(() => injectReferences(searchSourceJSON, references)).toThrowErrorMatchingInlineSnapshot(
`"Could not find reference for oops"`
);
});
test('handles filters', () => {
searchSourceJSON.filter = [
// @ts-expect-error
{ meta: { indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index' } },
];
const actual = injectReferences(searchSourceJSON, references);
expect(actual).toMatchInlineSnapshot(`
Object {
"filter": Array [
Object {
"meta": Object {
"index": "033af690-fde7-11ea-91f3-fb9e73f9bbe9",
},
},
],
"highlightAll": true,
"index": "033af690-fde7-11ea-91f3-fb9e73f9bbe9",
"query": Object {
"language": "kuery",
"query": "play_name:\\"Henry IV\\"",
},
"version": true,
}
`);
});
test('throws an error if there is a broken filter reference', () => {
// @ts-expect-error
searchSourceJSON.filter = [{ meta: { indexRefName: 'oops' } }];
expect(() => injectReferences(searchSourceJSON, references)).toThrowErrorMatchingInlineSnapshot(
`"Could not find reference for oops"`
);
});
});

View file

@ -18,15 +18,36 @@
*/
import { BehaviorSubject } from 'rxjs';
import { ApiResponse } from '@elastic/elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { FetchHandlers, SearchRequest } from '../fetch';
interface MsearchHeaders {
index: string;
preference?: number | string;
}
interface MsearchRequest {
header: MsearchHeaders;
body: any;
}
// @internal
export interface MsearchRequestBody {
searches: MsearchRequest[];
}
// @internal
export interface MsearchResponse {
body: ApiResponse<{ responses: Array<SearchResponse<any>> }>;
}
// @internal
export interface LegacyFetchHandlers {
callMsearch: (params: {
body: SearchRequest;
body: MsearchRequestBody;
signal: AbortSignal;
}) => Promise<Array<SearchResponse<any>>>;
}) => Promise<MsearchResponse>;
loadingCount$: BehaviorSubject<number>;
}

View file

@ -20,8 +20,8 @@
import { BehaviorSubject } from 'rxjs';
import { uiSettingsServiceMock } from '../../../../../core/public/mocks';
import { ISearchSource, SearchSource } from './search_source';
import { SearchSourceFields } from './types';
import { SearchSource } from './search_source';
import { ISearchStartSearchSource, ISearchSource, SearchSourceFields } from './types';
export const searchSourceInstanceMock: MockedKeys<ISearchSource> = {
setPreferredSearchStrategyId: jest.fn(),
@ -45,7 +45,7 @@ export const searchSourceInstanceMock: MockedKeys<ISearchSource> = {
serialize: jest.fn(),
};
export const searchSourceMock = {
export const searchSourceCommonMock: jest.Mocked<ISearchStartSearchSource> = {
create: jest.fn().mockReturnValue(searchSourceInstanceMock),
createEmpty: jest.fn().mockReturnValue(searchSourceInstanceMock),
};

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { Observable, BehaviorSubject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { IndexPattern } from '../../index_patterns';
import { GetConfigFn } from '../../types';
import { fetchSoon } from './legacy';
@ -53,16 +53,7 @@ describe('SearchSource', () => {
let searchSourceDependencies: SearchSourceDependencies;
beforeEach(() => {
mockSearchMethod = jest.fn(() => {
return new Observable((subscriber) => {
setTimeout(() => {
subscriber.next({
rawResponse: '',
});
subscriber.complete();
}, 100);
});
});
mockSearchMethod = jest.fn().mockResolvedValue({ rawResponse: '' });
searchSourceDependencies = {
getConfig: jest.fn(),

View file

@ -71,22 +71,15 @@
import { setWith } from '@elastic/safer-lodash-set';
import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash';
import { map } from 'rxjs/operators';
import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/common';
import { IIndexPattern } from '../../index_patterns';
import { ISearchGeneric } from '../..';
import { SearchSourceOptions, SearchSourceFields } from './types';
import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../..';
import { ISearchSource, SearchSourceOptions, SearchSourceFields } from './types';
import { FetchHandlers, RequestFailure, getSearchParamsFromRequest, SearchRequest } from './fetch';
import {
getEsQueryConfig,
buildEsQuery,
Filter,
UI_SETTINGS,
ISearchOptions,
} from '../../../common';
import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common';
import { getHighlightRequest } from '../../../common/field_formats';
import { fetchSoon } from './legacy';
import { extractReferences } from './extract_references';
@ -108,7 +101,7 @@ export const searchSourceRequiredUiSettings = [
];
export interface SearchSourceDependencies extends FetchHandlers {
search: ISearchGeneric;
search: (request: IEsSearchRequest, options: ISearchOptions) => Promise<IEsSearchResponse>;
}
/** @public **/
@ -264,7 +257,7 @@ export class SearchSource {
if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) {
response = await this.legacyFetch(searchRequest, options);
} else {
response = await this.fetch$(searchRequest, options).toPromise();
response = await this.fetchSearch(searchRequest, options);
}
// TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved
@ -308,17 +301,17 @@ export class SearchSource {
/**
* Run a search using the search service
* @return {Observable<SearchResponse<unknown>>}
* @return {Promise<SearchResponse<unknown>>}
*/
private fetch$(searchRequest: SearchRequest, options: ISearchOptions) {
private fetchSearch(searchRequest: SearchRequest, options: ISearchOptions) {
const { search, getConfig, onResponse } = this.dependencies;
const params = getSearchParamsFromRequest(searchRequest, {
getConfig,
});
return search({ params, indexType: searchRequest.indexType }, options).pipe(
map(({ rawResponse }) => onResponse(searchRequest, rawResponse))
return search({ params, indexType: searchRequest.indexType }, options).then(({ rawResponse }) =>
onResponse(searchRequest, rawResponse)
);
}
@ -558,9 +551,3 @@ export class SearchSource {
return [filterField];
}
}
/**
* search source interface
* @public
*/
export type ISearchSource = Pick<SearchSource, keyof SearchSource>;

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BehaviorSubject } from 'rxjs';
import { IndexPatternsContract } from '../../index_patterns/index_patterns';
import { SearchSourceService, SearchSourceDependencies } from './';
describe('SearchSource service', () => {
let dependencies: jest.Mocked<SearchSourceDependencies>;
beforeEach(() => {
jest.resetModules();
dependencies = {
getConfig: jest.fn(),
search: jest.fn(),
onResponse: jest.fn(),
legacy: {
callMsearch: jest.fn(),
loadingCount$: new BehaviorSubject(0),
},
};
});
describe('start()', () => {
test('exposes proper contract', () => {
const start = new SearchSourceService().start(
(jest.fn() as unknown) as jest.Mocked<IndexPatternsContract>,
dependencies
);
expect(Object.keys(start)).toEqual(['create', 'createEmpty']);
});
});
});

View file

@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createSearchSource, SearchSource, SearchSourceDependencies } from './';
import { IndexPatternsContract } from '../../index_patterns/index_patterns';
export class SearchSourceService {
public setup() {}
public start(indexPatterns: IndexPatternsContract, dependencies: SearchSourceDependencies) {
return {
/**
* creates searchsource based on serialized search source fields
*/
create: createSearchSource(indexPatterns, dependencies),
/**
* creates an enpty search source
*/
createEmpty: () => {
return new SearchSource({}, dependencies);
},
};
}
public stop() {}
}

View file

@ -16,9 +16,32 @@
* specific language governing permissions and limitations
* under the License.
*/
import { NameList } from 'elasticsearch';
import { IndexPattern, Query } from '../..';
import { Filter } from '../../../common';
import { Filter, IndexPattern, Query } from '../..';
import { SearchSource } from './search_source';
/**
* search source interface
* @public
*/
export type ISearchSource = Pick<SearchSource, keyof SearchSource>;
/**
* high level search service
* @public
*/
export interface ISearchStartSearchSource {
/**
* creates {@link SearchSource} based on provided serialized {@link SearchSourceFields}
* @param fields
*/
create: (fields?: SearchSourceFields) => Promise<ISearchSource>;
/**
* creates empty {@link SearchSource}
*/
createEmpty: () => ISearchSource;
}
export type EsQuerySearchAfter = [string | number, string | number];
@ -75,8 +98,6 @@ export interface SearchSourceOptions {
callParentStartHandlers?: boolean;
}
export { ISearchSource } from './search_source';
export interface SortOptions {
mode?: 'min' | 'max' | 'sum' | 'avg' | 'median';
type?: 'double' | 'long' | 'date' | 'date_nanos';

View file

@ -78,7 +78,7 @@ const createStartContract = (): Start => {
};
};
export { createSearchSourceMock } from './search/mocks';
export { createSearchSourceMock } from '../common/search/search_source/mocks';
export { getCalculateAutoTimeExpression } from '../common/search/aggs';
export const dataPluginMock = {

View file

@ -7,7 +7,8 @@
import { $Values } from '@kbn/utility-types';
import _ from 'lodash';
import { Action } from 'history';
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
import { ApiResponse } from '@elastic/elasticsearch';
import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transport';
import { ApplicationStart } from 'kibana/public';
import { Assign } from '@kbn/utility-types';
import { BehaviorSubject } from 'rxjs';

View file

@ -18,10 +18,8 @@
*/
import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks';
import { searchSourceMock } from './search_source/mocks';
import { ISearchSetup, ISearchStart } from './types';
import { searchSourceMock, createSearchSourceMock } from '../../common/search/search_source/mocks';
export * from '../../common/search/search_source/mocks';
function createSetupContract(): jest.Mocked<ISearchSetup> {
return {
@ -35,7 +33,7 @@ function createStartContract(): jest.Mocked<ISearchStart> {
aggs: searchAggsStartMock(),
search: jest.fn(),
showError: jest.fn(),
searchSource: searchSourceMock,
searchSource: searchSourceMock.createStartContract(),
};
}
@ -43,5 +41,3 @@ export const searchServiceMock = {
createSetupContract,
createStartContract,
};
export { createSearchSourceMock };

View file

@ -52,6 +52,7 @@ describe('Search service', () => {
describe('start()', () => {
it('exposes proper contract', async () => {
const start = searchService.start(mockCoreStart, {
fieldFormats: {},
indexPatterns: {},
} as any);
expect(start).toHaveProperty('aggs');

View file

@ -23,9 +23,10 @@ import { ISearchSetup, ISearchStart, SearchEnhancements } from './types';
import { handleResponse } from './fetch';
import {
createSearchSource,
IEsSearchRequest,
ISearchGeneric,
SearchSource,
ISearchOptions,
SearchSourceService,
SearchSourceDependencies,
} from '../../common/search';
import { getCallMsearch } from './legacy';
@ -57,6 +58,7 @@ export interface SearchServiceStartDependencies {
export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private readonly aggsService = new AggsService();
private readonly searchSourceService = new SearchSourceService();
private searchInterceptor!: ISearchInterceptor;
private usageCollector?: SearchUsageCollector;
@ -115,7 +117,9 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
const searchSourceDependencies: SearchSourceDependencies = {
getConfig: uiSettings.get.bind(uiSettings),
search,
search: (request: IEsSearchRequest, options: ISearchOptions) => {
return search(request, options).toPromise();
},
onResponse: handleResponse,
legacy: {
callMsearch: getCallMsearch({ http }),
@ -129,22 +133,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
showError: (e: Error) => {
this.searchInterceptor.showError(e);
},
searchSource: {
/**
* creates searchsource based on serialized search source fields
*/
create: createSearchSource(indexPatterns, searchSourceDependencies),
/**
* creates an enpty search source
*/
createEmpty: () => {
return new SearchSource({}, searchSourceDependencies);
},
},
searchSource: this.searchSourceService.start(indexPatterns, searchSourceDependencies),
};
}
public stop() {
this.aggsService.stop();
this.searchSourceService.stop();
}
}

View file

@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { searchSourceCommonMock } from '../../../common/search/search_source/mocks';
import { ISearchStart } from '../types';
function createStartContract(): jest.Mocked<ISearchStart['searchSource']> {
return searchSourceCommonMock;
}
export const searchSourceMock = {
createStartContract,
};

View file

@ -21,10 +21,12 @@ import { PackageInfo } from 'kibana/server';
import { ISearchInterceptor } from './search_interceptor';
import { SearchUsageCollector } from './collectors';
import { AggsSetup, AggsSetupDependencies, AggsStartDependencies, AggsStart } from './aggs';
import { ISearchGeneric, ISearchSource, SearchSourceFields } from '../../common/search';
import { ISearchGeneric, ISearchStartSearchSource } from '../../common/search';
import { IndexPatternsContract } from '../../common/index_patterns/index_patterns';
import { UsageCollectionSetup } from '../../../usage_collection/public';
export { ISearchStartSearchSource };
export interface SearchEnhancements {
searchInterceptor: ISearchInterceptor;
}
@ -42,21 +44,6 @@ export interface ISearchSetup {
__enhance: (enhancements: SearchEnhancements) => void;
}
/**
* high level search service
* @public
*/
export interface ISearchStartSearchSource {
/**
* creates {@link SearchSource} based on provided serialized {@link SearchSourceFields}
* @param fields
*/
create: (fields?: SearchSourceFields) => Promise<ISearchSource>;
/**
* creates empty {@link SearchSource}
*/
createEmpty: () => ISearchSource;
}
/**
* search service
* @public

View file

@ -111,13 +111,15 @@ export class DataServerPlugin
public start(core: CoreStart) {
const fieldFormats = this.fieldFormats.start();
return {
search: this.searchService.start(core, { fieldFormats }),
const indexPatterns = this.indexPatterns.start(core, {
fieldFormats,
indexPatterns: this.indexPatterns.start(core, {
fieldFormats,
logger: this.logger.get('indexPatterns'),
}),
logger: this.logger.get('indexPatterns'),
});
return {
fieldFormats,
indexPatterns,
search: this.searchService.start(core, { fieldFormats, indexPatterns }),
};
}

View file

@ -19,6 +19,7 @@
import { ISearchSetup, ISearchStart } from './types';
import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks';
import { searchSourceMock } from './search_source/mocks';
export function createSearchSetupMock(): jest.Mocked<ISearchSetup> {
return {
@ -33,5 +34,6 @@ export function createSearchStartMock(): jest.Mocked<ISearchStart> {
aggs: searchAggsStartMock(),
getSearchStrategy: jest.fn(),
search: jest.fn(),
searchSource: searchSourceMock.createStartContract(),
};
}

View file

@ -0,0 +1,101 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Observable } from 'rxjs';
import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server';
import {
coreMock,
pluginInitializerContextConfigMock,
} from '../../../../../../src/core/server/mocks';
import { convertRequestBody, getCallMsearch } from './call_msearch';
describe('callMsearch', () => {
let esClient: DeeplyMockedKeys<IScopedClusterClient>;
let globalConfig$: Observable<SharedGlobalConfig>;
let uiSettings: IUiSettingsClient;
let callMsearch: ReturnType<typeof getCallMsearch>;
beforeEach(() => {
const coreContext = coreMock.createRequestHandlerContext();
esClient = coreContext.elasticsearch.client;
globalConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
uiSettings = coreContext.uiSettings.client;
callMsearch = getCallMsearch({ esClient, globalConfig$, uiSettings });
});
it('handler calls msearch with the given request', async () => {
const mockBody = {
searches: [{ header: { index: 'foo' }, body: { query: { match_all: {} } } }],
};
await callMsearch({
body: mockBody,
signal: new AbortController().signal,
});
expect(esClient.asCurrentUser.msearch).toHaveBeenCalledTimes(1);
expect(esClient.asCurrentUser.msearch.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"body": "{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\"}
{\\"query\\":{\\"match_all\\":{}}}
",
},
Object {
"querystring": Object {
"ignore_throttled": true,
"ignore_unavailable": true,
"max_concurrent_shard_requests": undefined,
},
},
]
`);
});
describe('convertRequestBody', () => {
it('combines header & body into proper msearch request', () => {
const request = {
searches: [{ header: { index: 'foo', preference: 0 }, body: { test: true } }],
};
expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(`
"{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0}
{\\"timeout\\":\\"30000ms\\",\\"test\\":true}
"
`);
});
it('handles multiple searches', () => {
const request = {
searches: [
{ header: { index: 'foo', preference: 0 }, body: { test: true } },
{ header: { index: 'bar', preference: 1 }, body: { hello: 'world' } },
],
};
expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(`
"{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0}
{\\"timeout\\":\\"30000ms\\",\\"test\\":true}
{\\"ignore_unavailable\\":true,\\"index\\":\\"bar\\",\\"preference\\":1}
{\\"timeout\\":\\"30000ms\\",\\"hello\\":\\"world\\"}
"
`);
});
});
});

View file

@ -0,0 +1,100 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { ApiResponse } from '@elastic/elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server';
import { MsearchRequestBody, MsearchResponse } from '../../../common/search/search_source';
import { shimHitsTotal } from './shim_hits_total';
import { getShardTimeout, getDefaultSearchParams, toSnakeCase } from '..';
/** @internal */
export function convertRequestBody(
requestBody: MsearchRequestBody,
{ timeout }: { timeout?: string }
): string {
return requestBody.searches.reduce((req, curr) => {
const header = JSON.stringify({
ignore_unavailable: true,
...curr.header,
});
const body = JSON.stringify({
timeout,
...curr.body,
});
return `${req}${header}\n${body}\n`;
}, '');
}
interface CallMsearchDependencies {
esClient: IScopedClusterClient;
globalConfig$: Observable<SharedGlobalConfig>;
uiSettings: IUiSettingsClient;
}
/**
* Helper for the `/internal/_msearch` route, exported separately here
* so that it can be reused elsewhere in the data plugin on the server,
* e.g. SearchSource
*
* @internal
*/
export function getCallMsearch(dependencies: CallMsearchDependencies) {
return async (params: {
body: MsearchRequestBody;
signal?: AbortSignal;
}): Promise<MsearchResponse> => {
const { esClient, globalConfig$, uiSettings } = dependencies;
// get shardTimeout
const config = await globalConfig$.pipe(first()).toPromise();
const timeout = getShardTimeout(config);
// trackTotalHits is not supported by msearch
const { trackTotalHits, ...defaultParams } = await getDefaultSearchParams(uiSettings);
const body = convertRequestBody(params.body, timeout);
// Temporary workaround until https://github.com/elastic/elasticsearch-js/issues/1297
const promise = esClient.asCurrentUser.msearch(
{
body,
},
{
querystring: toSnakeCase(defaultParams),
}
);
if (params.signal) {
params.signal.addEventListener('abort', () => promise.abort());
}
const response = (await promise) as ApiResponse<{ responses: Array<SearchResponse<any>> }>;
return {
body: {
...response,
body: {
responses: response.body.responses?.map((r: SearchResponse<any>) => shimHitsTotal(r)),
},
},
};
};
}

View file

@ -17,6 +17,7 @@
* under the License.
*/
export * from './call_msearch';
export * from './msearch';
export * from './search';
export * from './shim_hits_total';

View file

@ -30,7 +30,8 @@ import {
httpServerMock,
pluginInitializerContextConfigMock,
} from '../../../../../../src/core/server/mocks';
import { registerMsearchRoute, convertRequestBody } from './msearch';
import { convertRequestBody } from './call_msearch';
import { registerMsearchRoute } from './msearch';
import { DataPluginStart } from '../../plugin';
import { dataPluginMock } from '../../mocks';
@ -49,7 +50,7 @@ describe('msearch route', () => {
it('handler calls /_msearch with the given request', async () => {
const response = { id: 'yay', body: { responses: [{ hits: { total: 5 } }] } };
const mockClient = { transport: { request: jest.fn().mockResolvedValue(response) } };
const mockClient = { msearch: jest.fn().mockResolvedValue(response) };
const mockContext = {
core: {
elasticsearch: { client: { asCurrentUser: mockClient } },
@ -70,11 +71,16 @@ describe('msearch route', () => {
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
expect(mockClient.transport.request.mock.calls[0][0].method).toBe('GET');
expect(mockClient.transport.request.mock.calls[0][0].path).toBe('/_msearch');
expect(mockClient.transport.request.mock.calls[0][0].body).toEqual(
expect(mockClient.msearch.mock.calls[0][0].body).toEqual(
convertRequestBody(mockBody as any, {})
);
expect(mockClient.msearch.mock.calls[0][1].querystring).toMatchInlineSnapshot(`
Object {
"ignore_throttled": true,
"ignore_unavailable": true,
"max_concurrent_shard_requests": undefined,
}
`);
expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({
body: response,
@ -89,7 +95,7 @@ describe('msearch route', () => {
},
};
const mockClient = {
transport: { request: jest.fn().mockReturnValue(Promise.reject(response)) },
msearch: jest.fn().mockReturnValue(Promise.reject(response)),
};
const mockContext = {
core: {
@ -111,40 +117,11 @@ describe('msearch route', () => {
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
expect(mockClient.transport.request).toBeCalled();
expect(mockClient.msearch).toBeCalled();
expect(mockResponse.customError).toBeCalled();
const error: any = mockResponse.customError.mock.calls[0][0];
expect(error.body.message).toBe('oh no');
expect(error.body.attributes.error).toBe('oops');
});
describe('convertRequestBody', () => {
it('combines header & body into proper msearch request', () => {
const request = {
searches: [{ header: { index: 'foo', preference: 0 }, body: { test: true } }],
};
expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(`
"{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0}
{\\"timeout\\":\\"30000ms\\",\\"test\\":true}
"
`);
});
it('handles multiple searches', () => {
const request = {
searches: [
{ header: { index: 'foo', preference: 0 }, body: { test: true } },
{ header: { index: 'bar', preference: 1 }, body: { hello: 'world' } },
],
};
expect(convertRequestBody(request, { timeout: '30000ms' })).toMatchInlineSnapshot(`
"{\\"ignore_unavailable\\":true,\\"index\\":\\"foo\\",\\"preference\\":0}
{\\"timeout\\":\\"30000ms\\",\\"test\\":true}
{\\"ignore_unavailable\\":true,\\"index\\":\\"bar\\",\\"preference\\":1}
{\\"timeout\\":\\"30000ms\\",\\"hello\\":\\"world\\"}
"
`);
});
});
});

View file

@ -17,46 +17,12 @@
* under the License.
*/
import { first } from 'rxjs/operators';
import { schema } from '@kbn/config-schema';
import { SearchResponse } from 'elasticsearch';
import { IRouter } from 'src/core/server';
import { SearchRouteDependencies } from '../search_service';
import { shimHitsTotal } from './shim_hits_total';
import { getShardTimeout, getDefaultSearchParams, toSnakeCase } from '..';
interface MsearchHeaders {
index: string;
preference?: number | string;
}
interface MsearchRequest {
header: MsearchHeaders;
body: any;
}
interface RequestBody {
searches: MsearchRequest[];
}
/** @internal */
export function convertRequestBody(
requestBody: RequestBody,
{ timeout }: { timeout?: string }
): string {
return requestBody.searches.reduce((req, curr) => {
const header = JSON.stringify({
ignore_unavailable: true,
...curr.header,
});
const body = JSON.stringify({
timeout,
...curr.body,
});
return `${req}${header}\n${body}\n`;
}, '');
}
import { getCallMsearch } from './call_msearch';
/**
* The msearch route takes in an array of searches, each consisting of header
@ -93,35 +59,15 @@ export function registerMsearchRoute(router: IRouter, deps: SearchRouteDependenc
},
},
async (context, request, res) => {
const client = context.core.elasticsearch.client.asCurrentUser;
// get shardTimeout
const config = await deps.globalConfig$.pipe(first()).toPromise();
const timeout = getShardTimeout(config);
const body = convertRequestBody(request.body, timeout);
// trackTotalHits is not supported by msearch
const { trackTotalHits, ...defaultParams } = await getDefaultSearchParams(
context.core.uiSettings.client
);
const callMsearch = getCallMsearch({
esClient: context.core.elasticsearch.client,
globalConfig$: deps.globalConfig$,
uiSettings: context.core.uiSettings.client,
});
try {
const response = await client.transport.request({
method: 'GET',
path: '/_msearch',
body,
querystring: toSnakeCase(defaultParams),
});
return res.ok({
body: {
...response,
body: {
responses: response.body.responses?.map((r: SearchResponse<any>) => shimHitsTotal(r)),
},
},
});
const response = await callMsearch({ body: request.body });
return res.ok(response);
} catch (err) {
return res.customError({
statusCode: err.statusCode || 500,

View file

@ -22,6 +22,7 @@ import { coreMock } from '../../../../core/server/mocks';
import { DataPluginStart } from '../plugin';
import { createFieldFormatsStartMock } from '../field_formats/mocks';
import { createIndexPatternsStartMock } from '../index_patterns/mocks';
import { SearchService, SearchServiceSetupDependencies } from './search_service';
@ -54,6 +55,7 @@ describe('Search service', () => {
it('exposes proper contract', async () => {
const start = plugin.start(mockCoreStart, {
fieldFormats: createFieldFormatsStartMock(),
indexPatterns: createIndexPatternsStartMock(),
});
expect(start).toHaveProperty('aggs');
expect(start).toHaveProperty('getSearchStrategy');

View file

@ -17,10 +17,12 @@
* under the License.
*/
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { pick } from 'lodash';
import {
CoreSetup,
CoreStart,
KibanaRequest,
Logger,
Plugin,
PluginInitializerContext,
@ -34,7 +36,8 @@ import { ISearchSetup, ISearchStart, ISearchStrategy, SearchEnhancements } from
import { AggsService, AggsSetupDependencies } from './aggs';
import { FieldFormatsStart } from '../field_formats';
import { registerMsearchRoute, registerSearchRoute } from './routes';
import { IndexPatternsServiceStart } from '../index_patterns';
import { getCallMsearch, registerMsearchRoute, registerSearchRoute } from './routes';
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search';
import { DataPluginStart } from '../plugin';
import { UsageCollectionSetup } from '../../../usage_collection/server';
@ -47,13 +50,16 @@ import {
IEsSearchRequest,
IEsSearchResponse,
ISearchOptions,
} from '../../common';
SearchSourceDependencies,
SearchSourceService,
searchSourceRequiredUiSettings,
} from '../../common/search';
import {
getShardDelayBucketAgg,
SHARD_DELAY_AGG_NAME,
} from '../../common/search/aggs/buckets/shard_delay';
import { ConfigSchema } from '../../config';
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../../config';
type StrategyMap = Record<string, ISearchStrategy<any, any>>;
@ -66,6 +72,7 @@ export interface SearchServiceSetupDependencies {
/** @internal */
export interface SearchServiceStartDependencies {
fieldFormats: FieldFormatsStart;
indexPatterns: IndexPatternsServiceStart;
}
/** @internal */
@ -76,6 +83,7 @@ export interface SearchRouteDependencies {
export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private readonly aggsService = new AggsService();
private readonly searchSourceService = new SearchSourceService();
private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY;
private searchStrategies: StrategyMap = {};
@ -137,8 +145,8 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
};
}
public start(
{ uiSettings }: CoreStart,
{ fieldFormats }: SearchServiceStartDependencies
{ elasticsearch, savedObjects, uiSettings }: CoreStart,
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
): ISearchStart {
return {
aggs: this.aggsService.start({ fieldFormats, uiSettings }),
@ -150,6 +158,58 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
) => {
return this.search(context, searchRequest, options);
},
searchSource: {
asScoped: async (request: KibanaRequest) => {
const esClient = elasticsearch.client.asScoped(request);
const savedObjectsClient = savedObjects.getScopedClient(request);
const scopedIndexPatterns = await indexPatterns.indexPatternsServiceFactory(request);
const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient);
// cache ui settings, only including items which are explicitly needed by SearchSource
const uiSettingsCache = pick(
await uiSettingsClient.getAll(),
searchSourceRequiredUiSettings
);
const searchSourceDependencies: SearchSourceDependencies = {
getConfig: <T = any>(key: string): T => uiSettingsCache[key],
search: (searchRequest, options) => {
/**
* Unless we want all SearchSource users to provide both a KibanaRequest
* (needed for index patterns) AND the RequestHandlerContext (needed for
* low-level search), we need to fake the context as it can be derived
* from the request object anyway. This will pose problems for folks who
* are registering custom search strategies as they are only getting a
* subset of the entire context. Ideally low-level search should be
* refactored to only require the needed dependencies: esClient & uiSettings.
*/
const fakeRequestHandlerContext = {
core: {
elasticsearch: {
client: esClient,
},
uiSettings: {
client: uiSettingsClient,
},
},
} as RequestHandlerContext;
return this.search(fakeRequestHandlerContext, searchRequest, options);
},
// onResponse isn't used on the server, so we just return the original value
onResponse: (req, res) => res,
legacy: {
callMsearch: getCallMsearch({
esClient,
globalConfig$: this.initializerContext.config.legacy.globalConfig$,
uiSettings: uiSettingsClient,
}),
loadingCount$: new BehaviorSubject(0),
},
};
return this.searchSourceService.start(scopedIndexPatterns, searchSourceDependencies);
},
},
};
}

View file

@ -0,0 +1,35 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { KibanaRequest } from 'src/core/server';
import { searchSourceCommonMock } from '../../../common/search/search_source/mocks';
import { ISearchStart } from '../types';
function createStartContract(): MockedKeys<ISearchStart['searchSource']> {
return {
asScoped: async (request: jest.Mocked<KibanaRequest>) => {
return searchSourceCommonMock;
},
};
}
export const searchSourceMock = {
createStartContract,
};

View file

@ -17,8 +17,13 @@
* under the License.
*/
import { RequestHandlerContext } from '../../../../core/server';
import { ISearchOptions, IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search';
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
import {
ISearchOptions,
ISearchStartSearchSource,
IKibanaSearchRequest,
IKibanaSearchResponse,
} from '../../common/search';
import { AggsSetup, AggsStart } from './aggs';
import { SearchUsage } from './collectors';
import { IEsSearchRequest, IEsSearchResponse } from './es_search';
@ -69,6 +74,9 @@ export interface ISearchStart<
request: SearchStrategyRequest,
options: ISearchOptions
) => Promise<SearchStrategyResponse>;
searchSource: {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
};
}
/**

View file

@ -5,172 +5,51 @@
```ts
import { $Values } from '@kbn/utility-types';
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
import { ApiResponse } from '@elastic/elasticsearch';
import { Assign } from '@kbn/utility-types';
import Boom from 'boom';
import { BulkIndexDocumentsParams } from 'elasticsearch';
import { CatAliasesParams } from 'elasticsearch';
import { CatAllocationParams } from 'elasticsearch';
import { CatCommonParams } from 'elasticsearch';
import { CatFielddataParams } from 'elasticsearch';
import { CatHealthParams } from 'elasticsearch';
import { CatHelpParams } from 'elasticsearch';
import { CatIndicesParams } from 'elasticsearch';
import { CatRecoveryParams } from 'elasticsearch';
import { CatSegmentsParams } from 'elasticsearch';
import { CatShardsParams } from 'elasticsearch';
import { CatSnapshotsParams } from 'elasticsearch';
import { CatTasksParams } from 'elasticsearch';
import { CatThreadPoolParams } from 'elasticsearch';
import { ClearScrollParams } from 'elasticsearch';
import { Client } from 'elasticsearch';
import { ClusterAllocationExplainParams } from 'elasticsearch';
import { ClusterGetSettingsParams } from 'elasticsearch';
import { ClusterHealthParams } from 'elasticsearch';
import { ClusterPendingTasksParams } from 'elasticsearch';
import { ClusterPutSettingsParams } from 'elasticsearch';
import { ClusterRerouteParams } from 'elasticsearch';
import { ClusterStateParams } from 'elasticsearch';
import { ClusterStatsParams } from 'elasticsearch';
import { BehaviorSubject } from 'rxjs';
import { ConfigDeprecationProvider } from '@kbn/config';
import { CoreSetup } from 'src/core/server';
import { CoreSetup as CoreSetup_2 } from 'kibana/server';
import { CoreStart } from 'src/core/server';
import { CoreStart as CoreStart_2 } from 'kibana/server';
import { CountParams } from 'elasticsearch';
import { CreateDocumentParams } from 'elasticsearch';
import { DeleteDocumentByQueryParams } from 'elasticsearch';
import { DeleteDocumentParams } from 'elasticsearch';
import { DeleteScriptParams } from 'elasticsearch';
import { DeleteTemplateParams } from 'elasticsearch';
import { Duration } from 'moment';
import { Ensure } from '@kbn/utility-types';
import { EnvironmentMode } from '@kbn/config';
import { ErrorToastOptions } from 'src/core/public/notifications';
import { ExistsParams } from 'elasticsearch';
import { ExplainParams } from 'elasticsearch';
import { ExpressionAstFunction } from 'src/plugins/expressions/common';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { FieldStatsParams } from 'elasticsearch';
import { GenericParams } from 'elasticsearch';
import { GetParams } from 'elasticsearch';
import { GetResponse } from 'elasticsearch';
import { GetScriptParams } from 'elasticsearch';
import { GetSourceParams } from 'elasticsearch';
import { GetTemplateParams } from 'elasticsearch';
import { IncomingHttpHeaders } from 'http';
import { IndexDocumentParams } from 'elasticsearch';
import { IndicesAnalyzeParams } from 'elasticsearch';
import { IndicesClearCacheParams } from 'elasticsearch';
import { IndicesCloseParams } from 'elasticsearch';
import { IndicesCreateParams } from 'elasticsearch';
import { IndicesDeleteAliasParams } from 'elasticsearch';
import { IndicesDeleteParams } from 'elasticsearch';
import { IndicesDeleteTemplateParams } from 'elasticsearch';
import { IndicesExistsAliasParams } from 'elasticsearch';
import { IndicesExistsParams } from 'elasticsearch';
import { IndicesExistsTemplateParams } from 'elasticsearch';
import { IndicesExistsTypeParams } from 'elasticsearch';
import { IndicesFlushParams } from 'elasticsearch';
import { IndicesFlushSyncedParams } from 'elasticsearch';
import { IndicesForcemergeParams } from 'elasticsearch';
import { IndicesGetAliasParams } from 'elasticsearch';
import { IndicesGetFieldMappingParams } from 'elasticsearch';
import { IndicesGetMappingParams } from 'elasticsearch';
import { IndicesGetParams } from 'elasticsearch';
import { IndicesGetSettingsParams } from 'elasticsearch';
import { IndicesGetTemplateParams } from 'elasticsearch';
import { IndicesGetUpgradeParams } from 'elasticsearch';
import { IndicesOpenParams } from 'elasticsearch';
import { IndicesPutAliasParams } from 'elasticsearch';
import { IndicesPutMappingParams } from 'elasticsearch';
import { IndicesPutSettingsParams } from 'elasticsearch';
import { IndicesPutTemplateParams } from 'elasticsearch';
import { IndicesRecoveryParams } from 'elasticsearch';
import { IndicesRefreshParams } from 'elasticsearch';
import { IndicesRolloverParams } from 'elasticsearch';
import { IndicesSegmentsParams } from 'elasticsearch';
import { IndicesShardStoresParams } from 'elasticsearch';
import { IndicesShrinkParams } from 'elasticsearch';
import { IndicesStatsParams } from 'elasticsearch';
import { IndicesUpdateAliasesParams } from 'elasticsearch';
import { IndicesUpgradeParams } from 'elasticsearch';
import { IndicesValidateQueryParams } from 'elasticsearch';
import { InfoParams } from 'elasticsearch';
import { IngestDeletePipelineParams } from 'elasticsearch';
import { IngestGetPipelineParams } from 'elasticsearch';
import { IngestPutPipelineParams } from 'elasticsearch';
import { IngestSimulateParams } from 'elasticsearch';
import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
import { ISearchSource } from 'src/plugins/data/public';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config';
import { KibanaRequest } from 'kibana/server';
import { LegacyAPICaller as LegacyAPICaller_2 } from 'kibana/server';
import { Logger } from '@kbn/logging';
import { Logger as Logger_2 } from 'kibana/server';
import { KibanaRequest } from 'src/core/server';
import { KibanaRequest as KibanaRequest_2 } from 'kibana/server';
import { LegacyAPICaller } from 'kibana/server';
import { Logger } from 'kibana/server';
import { LoggerFactory } from '@kbn/logging';
import { LogMeta } from '@kbn/logging';
import { MGetParams } from 'elasticsearch';
import { MGetResponse } from 'elasticsearch';
import { Moment } from 'moment';
import moment from 'moment';
import { MSearchParams } from 'elasticsearch';
import { MSearchResponse } from 'elasticsearch';
import { MSearchTemplateParams } from 'elasticsearch';
import { MTermVectorsParams } from 'elasticsearch';
import { NodesHotThreadsParams } from 'elasticsearch';
import { NodesInfoParams } from 'elasticsearch';
import { NodesStatsParams } from 'elasticsearch';
import { NameList } from 'elasticsearch';
import { Observable } from 'rxjs';
import { PackageInfo } from '@kbn/config';
import { PathConfigType } from '@kbn/utils';
import { PingParams } from 'elasticsearch';
import { Plugin as Plugin_2 } from 'src/core/server';
import { Plugin as Plugin_3 } from 'kibana/server';
import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server';
import { PutScriptParams } from 'elasticsearch';
import { PutTemplateParams } from 'elasticsearch';
import { RecursiveReadonly } from '@kbn/utility-types';
import { ReindexParams } from 'elasticsearch';
import { ReindexRethrottleParams } from 'elasticsearch';
import { RenderSearchTemplateParams } from 'elasticsearch';
import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestHandlerContext } from 'src/core/server';
import { RequestStatistics } from 'src/plugins/inspector/common';
import { SavedObject } from 'src/core/server';
import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'src/core/server';
import { ScrollParams } from 'elasticsearch';
import { SavedObjectsClientContract } from 'src/core/server';
import { Search } from '@elastic/elasticsearch/api/requestParams';
import { SearchParams } from 'elasticsearch';
import { SearchResponse } from 'elasticsearch';
import { SearchShardsParams } from 'elasticsearch';
import { SearchTemplateParams } from 'elasticsearch';
import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common';
import { ShardsResponse } from 'elasticsearch';
import { SnapshotCreateParams } from 'elasticsearch';
import { SnapshotCreateRepositoryParams } from 'elasticsearch';
import { SnapshotDeleteParams } from 'elasticsearch';
import { SnapshotDeleteRepositoryParams } from 'elasticsearch';
import { SnapshotGetParams } from 'elasticsearch';
import { SnapshotGetRepositoryParams } from 'elasticsearch';
import { SnapshotRestoreParams } from 'elasticsearch';
import { SnapshotStatusParams } from 'elasticsearch';
import { SnapshotVerifyRepositoryParams } from 'elasticsearch';
import { SuggestParams } from 'elasticsearch';
import { TasksCancelParams } from 'elasticsearch';
import { TasksGetParams } from 'elasticsearch';
import { TasksListParams } from 'elasticsearch';
import { TermvectorsParams } from 'elasticsearch';
import { ToastInputFields } from 'src/core/public/notifications';
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { Unit } from '@elastic/datemath';
import { UnwrapPromiseOrReturn } from '@kbn/utility-types';
import { UpdateDocumentByQueryParams } from 'elasticsearch';
import { UpdateDocumentParams } from 'elasticsearch';
// Warning: (ae-forgotten-export) The symbol "AggConfigSerialized" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "AggConfigOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@ -767,7 +646,7 @@ export const indexPatterns: {
//
// @public (undocumented)
export class IndexPatternsFetcher {
constructor(callDataCluster: LegacyAPICaller_2);
constructor(callDataCluster: LegacyAPICaller);
getFieldsForTimePattern(options: {
pattern: string;
metaFields: string[];
@ -794,7 +673,7 @@ export class IndexPatternsService implements Plugin_3<void, IndexPatternsService
//
// (undocumented)
start(core: CoreStart_2, { fieldFormats, logger }: IndexPatternsServiceStartDeps): {
indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise<IndexPatternsService_2>;
indexPatternsServiceFactory: (kibanaRequest: KibanaRequest_2) => Promise<IndexPatternsService_2>;
};
}
@ -831,10 +710,12 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
// (undocumented)
aggs: AggsStart;
getSearchStrategy: (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
// Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts
//
// (undocumented)
search: (context: RequestHandlerContext, request: SearchStrategyRequest, options: ISearchOptions) => Promise<SearchStrategyResponse>;
// (undocumented)
searchSource: {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
};
}
// Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@ -992,13 +873,13 @@ export class Plugin implements Plugin_2<PluginSetup, PluginStart, DataPluginSetu
};
// (undocumented)
start(core: CoreStart): {
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest<unknown, unknown, unknown, any>) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
// (undocumented)
stop(): void;
@ -1254,6 +1135,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage;
// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/search/types.ts:78:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)

View file

@ -0,0 +1,9 @@
{
"id": "data_search_plugin",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["data_search_test_plugin"],
"server": true,
"ui": false,
"requiredPlugins": ["data"]
}

View file

@ -0,0 +1,15 @@
{
"name": "data_search_plugin",
"version": "1.0.0",
"kibana": {
"version": "kibana"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "4.0.2"
}
}

View file

@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { PluginInitializer } from 'src/core/server';
import { DataSearchTestPlugin, TestPluginSetup, TestPluginStart } from './plugin';
export const plugin: PluginInitializer<TestPluginSetup, TestPluginStart> = () =>
new DataSearchTestPlugin();

View file

@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { CoreSetup, Plugin } from 'kibana/server';
import { schema } from '@kbn/config-schema';
import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server';
export interface DataSearchTestStartDeps {
data: DataPluginStart;
}
export class DataSearchTestPlugin
implements Plugin<TestPluginSetup, TestPluginStart, {}, DataSearchTestStartDeps> {
public setup(core: CoreSetup<DataSearchTestStartDeps>) {
const router = core.http.createRouter();
router.get(
{ path: '/api/data_search_plugin/search_source/as_scoped', validate: false },
async (context, req, res) => {
const [, { data }] = await core.getStartServices();
// Just make sure `asScoped` can be called
await data.search.searchSource.asScoped(req);
return res.ok();
}
);
router.get(
{ path: '/api/data_search_plugin/search_source/create_empty', validate: false },
async (context, req, res) => {
const [, { data }] = await core.getStartServices();
const service = await data.search.searchSource.asScoped(req);
const searchSource = service.createEmpty();
return res.ok({ body: searchSource.serialize() });
}
);
router.post(
{
path: '/api/data_search_plugin/search_source/create',
validate: {
body: schema.object({}, { unknowns: 'allow' }),
},
},
async (context, req, res) => {
const [, { data }] = await core.getStartServices();
const service = await data.search.searchSource.asScoped(req);
// Since the index pattern ID can change on each test run, we need
// to look it up on the fly and insert it into the request.
const indexPatterns = await data.indexPatterns.indexPatternsServiceFactory(req);
const ids = await indexPatterns.getIds();
// @ts-expect-error Force overwriting the request
req.body.index = ids[0];
const searchSource = await service.create(req.body);
return res.ok({ body: searchSource.serialize() });
}
);
}
public start() {}
public stop() {}
}
export type TestPluginSetup = ReturnType<DataSearchTestPlugin['setup']>;
export type TestPluginStart = ReturnType<DataSearchTestPlugin['start']>;

View file

@ -0,0 +1,15 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"server/**/*.ts",
"../../../../typings/**/*"
],
"exclude": [],
"references": [
{ "path": "../../../../src/core/tsconfig.json" }
]
}

View file

@ -17,9 +17,25 @@
* under the License.
*/
// @ts-expect-error
export default function ({ loadTestFile }) {
import { PluginFunctionalProviderContext } from '../../services';
export default function ({
getPageObjects,
getService,
loadTestFile,
}: PluginFunctionalProviderContext) {
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects(['common', 'header', 'settings']);
describe('data plugin', () => {
before(async () => {
await esArchiver.loadIfNeeded(
'../functional/fixtures/es_archiver/getting_started/shakespeare'
);
await PageObjects.common.navigateToApp('settings');
await PageObjects.settings.createIndexPattern('shakespeare', '');
});
loadTestFile(require.resolve('./index_patterns'));
loadTestFile(require.resolve('./search'));
});
}

View file

@ -20,20 +20,11 @@ import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
import '../../plugins/core_provider_plugin/types';
export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) {
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects(['common', 'settings']);
describe('index patterns', function () {
let indexPatternId = '';
before(async () => {
await esArchiver.loadIfNeeded(
'../functional/fixtures/es_archiver/getting_started/shakespeare'
);
await PageObjects.common.navigateToApp('settings');
await PageObjects.settings.createIndexPattern('shakespeare', '');
});
it('can get all ids', async () => {
const body = await (await supertest.get('/api/index-patterns-plugin/get-all').expect(200))

View file

@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
describe('data.search', () => {
describe('searchSource', () => {
const searchSourceFields = {
highlightAll: true,
index: '',
query: {
language: 'kuery',
query: 'play_name:\\"Henry IV\\"',
},
version: true,
};
it('asScoped()', async () => {
await supertest.get('/api/data_search_plugin/search_source/as_scoped').expect(200);
});
it('createEmpty()', async () => {
await supertest
.get('/api/data_search_plugin/search_source/create_empty')
.expect(200)
.expect(JSON.stringify({ searchSourceJSON: '{}', references: [] }));
});
it('create()', async () => {
await supertest
.post('/api/data_search_plugin/search_source/create')
.set('kbn-xsrf', 'anything')
.send(searchSourceFields)
.expect(200)
.expect(({ body }) => {
const searchSourceJSON = JSON.parse(body.searchSourceJSON);
expect(Object.keys(body)).to.eql(['searchSourceJSON', 'references']);
expect(searchSourceJSON.query).to.eql(searchSourceFields.query);
expect(body.references[0].type).to.equal('index-pattern');
expect(searchSourceJSON.indexRefName).to.equal(body.references[0].name);
});
});
});
});
}