Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[skip ci] Data access service #38737

Closed
wants to merge 11 commits into from
20 changes: 20 additions & 0 deletions src/legacy/core_plugins/data/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* under the License.
*/

import { SearchParams } from 'elasticsearch';

/** @public static code */
export { dateHistogramInterval } from './date_histogram_interval';
/** @public static code */
Expand All @@ -27,3 +29,21 @@ export {
parseEsInterval,
ParsedInterval,
} from './parse_es_interval';

export interface SearchArguments {
// The Elasticsearch search parameters
searchParams: SearchParams;

// An optional signal to abort the request
signal?: AbortSignal;

// Additional search options to customize the behavior of the request
options?: SearchOptions;
}

export interface SearchOptions {
[propName: string]: any;

// If courier:setRequestPreference is set to "sessionId", this will be used as the preference
sessionId?: string;
}
15 changes: 14 additions & 1 deletion src/legacy/core_plugins/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
*/

import { resolve } from 'path';
import { registerSearchApi } from './server/routes/search';
import {
getSearchParams,
defaultSearchStrategyProvider,
setSearchStrategy,
search,
} from './server/lib';
import { Legacy } from '../../../../kibana';
import { mappings } from './mappings';
import { SavedQuery } from './public';
Expand All @@ -33,7 +40,13 @@ export default function DataPlugin(kibana: any) {
enabled: Joi.boolean().default(true),
}).default();
},
init: (server: Legacy.Server) => ({}),
init: (server: Legacy.Server) => {
registerSearchApi(server);
setSearchStrategy(defaultSearchStrategyProvider(server));
server.expose('getSearchParams', getSearchParams);
server.expose('setSearchStrategy', setSearchStrategy);
server.expose('search', search);
},
uiExports: {
injectDefaultVars: () => ({}),
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
Expand Down
1 change: 1 addition & 0 deletions src/legacy/core_plugins/data/public/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
export { SearchService, SearchSetup } from './search_service';

export * from './search_bar';
export * from './search';
76 changes: 76 additions & 0 deletions src/legacy/core_plugins/data/public/search/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 { kfetch } from 'ui/kfetch';
import { from } from 'rxjs';
import { SearchArguments, SearchOptions } from '../../common';

/**
* The client-side API for making requests to Elasticsearch using raw Elasticsearch request DSL.
*
* @example
* const index = 'twitter';
* const body = { query: { match_all: {} } };
* const results = await search({
* searchParams: { index, body }
* }).toPromise();
*
* @example
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 1000);
* const index = 'twitter';
* const body = { query: { match_all: {} } };
* const results$ = search({
* searchParams: { index, body },
* signal
* });
* results$.subscribe({
* next: response => {
* console.log(response._shards.successful / response._shards.total);
* },
* complete: response => {
* console.log('got response: ', response);
* },
* error: error => {
* console.log('got error: ', error);
* }
* });
*/
export function search({ searchParams, signal, options = {} }: SearchArguments) {
const query = getSearchOptions(options);
const promise = kfetch({
method: 'POST',
pathname: `../api/search`,
query,
body: JSON.stringify(searchParams),
signal: typeof signal !== 'undefined' ? signal : null,
});
return from(promise);
}

// Not really a "session" ID per se, but just something unique to this browser load so that
// requests can hit the same shards if the `preference` setting is set to this
const sessionId = `${Date.now()}`;

export function getSearchOptions(options: SearchOptions) {
return {
sessionId,
...options,
};
}
2 changes: 2 additions & 0 deletions src/legacy/core_plugins/data/public/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { SavedObjectsClientContract } from 'src/core/public';
import { search } from './search';
import { createSavedQueryService } from './search_bar/lib/saved_query_service';

/**
Expand All @@ -28,6 +29,7 @@ import { createSavedQueryService } from './search_bar/lib/saved_query_service';
export class SearchService {
public setup(savedObjectsClient: SavedObjectsClientContract) {
return {
search,
services: {
savedQueryService: createSavedQueryService(savedObjectsClient),
},
Expand Down
37 changes: 37 additions & 0 deletions src/legacy/core_plugins/data/server/lib/default_search_strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { Server, Request } from 'hapi';
import { from } from 'rxjs';
import { SearchArguments } from '../../common';
import { getSearchParams } from './get_search_params';

export function defaultSearchStrategyProvider(server: Server) {
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
return function defaultSearchStrategy(
request: Request,
{ searchParams, signal, options = {} }: SearchArguments
) {
const searchParamsPromise = getSearchParams(request, searchParams, options);
const responsePromise = searchParamsPromise.then(params =>
callWithRequest(request, 'search', params, { signal })
);
return from(responsePromise);
};
}
78 changes: 78 additions & 0 deletions src/legacy/core_plugins/data/server/lib/get_search_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { first, map } from 'rxjs/operators';
import { Request } from 'hapi';
import KbnServer from 'src/legacy/server/kbn_server';
import { SearchParams } from 'elasticsearch';
import { SearchOptions } from '../../common';

export async function getSearchParams(
request: Request,
searchParams: SearchParams,
options: SearchOptions
) {
return {
...searchParams,
timeout: searchParams.hasOwnProperty('timeout')
? searchParams.timeout
: await getShardTimeout(request),
preference: searchParams.hasOwnProperty('preference')
? searchParams.preference
: await getPreference(request, options),
maxConcurrentShardRequests: searchParams.hasOwnProperty('maxConcurrentShardRequests')
? (searchParams as any).maxConcurrentShardRequests
Copy link
Contributor

Choose a reason for hiding this comment

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

Any idea why maxConcurrentShardRequests isn't in the SearchParams type? Is it an oversight in the typing or is this a kibana specific param?

Copy link
Contributor

Choose a reason for hiding this comment

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

actually I see it's only part of msearch. I guess for search endpoint it's irrelevant.

: await getMaxConcurrentShardRequests(request),
ignoreThrottled: searchParams.hasOwnProperty('ignoreThrottled')
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this can get moved to the x-pack search strategy. Maybe getSearchParams should be part of the search strategy? e.g.

export interface ISearchStrategy {
  search: (request: Request, searchArguments: SearchArguments) => Observable<SearchResponse<any>>;
  getSearchParams: (
    request: Request,
    searchParams: SearchParams,
    options: SearchOptions
  ) => SearchParams;
}

Then the x-pack implementation can use the default one plus incorporate some additional settings that are commercial only?

? (searchParams as any).ignoreThrottled
: await getIgnoreThrottled(request),
};
}

export async function getShardTimeout(request: Request) {
const kbnServer = (request.server as unknown) as KbnServer;
const shardTimeout$ = kbnServer.newPlatform.setup.core.elasticsearch.legacy.config$.pipe(
first(),
map(config => config.shardTimeout.asMilliseconds())
);
const timeout = await shardTimeout$.toPromise();
return `${timeout}ms`;
}

export async function getPreference(request: Request, { sessionId }: SearchOptions) {
const config = request.getUiSettingsService();
const setRequestPreferenceTo = await config.get('courier:setRequestPreference');
if (setRequestPreferenceTo === 'sessionId') {
return sessionId;
} else if (setRequestPreferenceTo === 'custom') {
return config.get('courier:customRequestPreference');
}
}

export async function getMaxConcurrentShardRequests(request: Request) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is returning any type because of config.get. If we know the shape of return value, maybe it can be typed out explicitly here?

const config = request.getUiSettingsService();
const maxConcurrentShardRequests = await config.get('courier:maxConcurrentShardRequests');
if (maxConcurrentShardRequests !== 0) return maxConcurrentShardRequests;
}

// TODO: Move to a plugin
Copy link
Contributor

Choose a reason for hiding this comment

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

ah, I see this comment now!

export function getIgnoreThrottled(request: Request) {
const config = request.getUiSettingsService();
return !config.get('search:includeFrozen');
}
23 changes: 23 additions & 0 deletions src/legacy/core_plugins/data/server/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.
*/

export * from './get_search_params';
export * from './default_search_strategy';
export * from './search';
export * from './search_strategy';
61 changes: 61 additions & 0 deletions src/legacy/core_plugins/data/server/lib/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 { Request } from 'hapi';
import { SearchArguments } from '../../common';
import { getSearchStrategy } from './search_strategy';

/**
* The server-side API for making requests to Elasticsearch using raw Elasticsearch request DSL.
*
* @example
* const index = 'twitter';
* const body = { query: { match_all: {} } };
* const results = await search(request, {
* searchParams: { index, body }
* }).toPromise();
*
* @example
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 1000);
* const index = 'twitter';
* const body = { query: { match_all: {} } };
* const results$ = search(request, {
* searchParams: { index, body },
* signal
* });
* results$.subscribe({
* next: response => {
* console.log(response._shards.successful / response._shards.total);
* },
* complete: response => {
* console.log('got response: ', response);
* },
* error: error => {
* console.log('got error: ', error);
* }
* });
*/
export function search(request: Request, searchArguments: SearchArguments) {
const searchStrategy = getSearchStrategy();
if (typeof searchStrategy !== 'function') {
throw new Error(`No search strategy registered`);
}
return searchStrategy(request, searchArguments);
}
38 changes: 38 additions & 0 deletions src/legacy/core_plugins/data/server/lib/search_strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { Request } from 'hapi';
import { Observable } from 'rxjs';
import { SearchResponse } from 'elasticsearch';
import { SearchArguments } from '../../common';

export type SearchStrategy = (
request: Request,
searchArguments: SearchArguments
) => Observable<SearchResponse<any>>;

let searchStrategy: SearchStrategy;

export function setSearchStrategy(strategy: SearchStrategy) {
searchStrategy = strategy;
}

export function getSearchStrategy() {
return searchStrategy;
}
Loading