Skip to content

Commit

Permalink
value suggestions server route -> data plugin
Browse files Browse the repository at this point in the history
Closes #52842
  • Loading branch information
alexwizp committed Dec 17, 2019
1 parent c4a0bd1 commit 1fcf01e
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 124 deletions.
2 changes: 0 additions & 2 deletions src/legacy/core_plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { exportApi } from './server/routes/api/export';
import { homeApi } from './server/routes/api/home';
import { managementApi } from './server/routes/api/management';
import { scriptsApi } from './server/routes/api/scripts';
import { registerSuggestionsApi } from './server/routes/api/suggestions';
import { registerKqlTelemetryApi } from './server/routes/api/kql_telemetry';
import { registerFieldFormats } from './server/field_formats/register';
import { registerTutorials } from './server/tutorials/register';
Expand Down Expand Up @@ -334,7 +333,6 @@ export default function(kibana) {
exportApi(server);
homeApi(server);
managementApi(server);
registerSuggestionsApi(server);
registerKqlTelemetryApi(server);
registerFieldFormats(server);
registerTutorials(server);
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions src/plugins/data/common/index_patterns/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { IFieldType } from './fields';

export interface IIndexPattern {
[key: string]: any;
fields: IFieldType[];
title: string;
id?: string;
Expand Down
29 changes: 29 additions & 0 deletions src/plugins/data/server/autocomplete/autocomplete_service.ts
Original file line number Diff line number Diff line change
@@ -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 { CoreSetup, Plugin } from 'kibana/server';
import { registerRoutes } from './routes';

export class AutocompleteService implements Plugin<void> {
public setup(core: CoreSetup) {
registerRoutes(core);
}

public start() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,4 @@
* under the License.
*/

import { registerValueSuggestions } from './register_value_suggestions';

export function registerSuggestionsApi(server) {
registerValueSuggestions(server);
}
export { AutocompleteService } from './autocomplete_service';
43 changes: 43 additions & 0 deletions src/plugins/data/server/autocomplete/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 } from 'rxjs/operators';
import {
APICaller,
CallAPIOptions,
CoreSetup,
ElasticsearchServiceSetup,
KibanaRequest,
} from 'kibana/server';
import { registerValueSuggestionsRoute } from './value_suggestions_route';

const getAPICallerFn = (elasticsearch: ElasticsearchServiceSetup) => async (
request: KibanaRequest
): Promise<APICaller> => {
const client = await elasticsearch.dataClient$.pipe(first()).toPromise();

return (endpoint: string, params?: Record<string, any>, options?: CallAPIOptions) =>
client.asScoped(request).callAsCurrentUser(endpoint, params, options);
};

export function registerRoutes({ http, elasticsearch }: CoreSetup): void {
const router = http.createRouter();

registerValueSuggestionsRoute(router, getAPICallerFn(elasticsearch));
}
138 changes: 138 additions & 0 deletions src/plugins/data/server/autocomplete/value_suggestions_route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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 { get, map } from 'lodash';
import { schema } from '@kbn/config-schema';
import { APICaller, IRouter, KibanaRequest } from 'kibana/server';

import { IFieldType, indexPatternsUtils, esFilters } from '../index';

export function registerValueSuggestionsRoute(
router: IRouter,
apiCaller: (request: KibanaRequest) => Promise<APICaller>
) {
router.post(
{
path: '/api/kibana/suggestions/values/{index}',
validate: {
params: schema.object(
{
index: schema.string(),
},
{ allowUnknowns: false }
),
body: schema.object(
{
field: schema.string(),
query: schema.string(),
boolFilter: schema.maybe(schema.any()),
},
{ allowUnknowns: false }
),
},
},
async (context, request, response) => {
const { client: uiSettings } = context.core.uiSettings;
const { field: fieldName, query, boolFilter } = request.body;
const { index } = request.params;

const autocompleteSearchOptions = {
timeout: await uiSettings.get<number>('kibana.autocompleteTimeout'),
terminate_after: await uiSettings.get<number>('kibana.autocompleteTerminateAfter'),
};

const indexPattern = await indexPatternsUtils.findIndexPatternById(
context.core.savedObjects.client,
index
);

const field = indexPatternsUtils.getFieldByName(fieldName, indexPattern);
const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter);

try {
const callCluster = await apiCaller(request);
const result = await callCluster('search', { index, body });

const buckets: any[] =
get(result, 'aggregations.suggestions.buckets') ||
get(result, 'aggregations.nestedSuggestions.suggestions.buckets');

return response.ok({ body: map(buckets || [], 'key') });
} catch (error) {
return response.internalError({ body: error });
}
}
);
}

async function getBody(
{ timeout, terminate_after }: Record<string, any>,
field: IFieldType | string,
query: string,
boolFilter: esFilters.Filter[] = []
) {
const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name);

// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators
const getEscapedQuery = (q: string = '') =>
q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, match => `\\${match}`);

// Helps ensure that the regex is not evaluated eagerly against the terms dictionary
const executionHint = 'map';

// We don't care about the accuracy of the counts, just the content of the terms, so this reduces
// the amount of information that needs to be transmitted to the coordinating node
const shardSize = 10;
const body = {
size: 0,
timeout,
terminate_after,
query: {
bool: {
filter: boolFilter,
},
},
aggs: {
suggestions: {
terms: {
field: isFieldObject(field) ? field.name : field,
include: `${getEscapedQuery(query)}.*`,
execution_hint: executionHint,
shard_size: shardSize,
},
},
},
};

if (isFieldObject(field) && field.subType && field.subType.nested) {
return {
...body,
aggs: {
nestedSuggestions: {
nested: {
path: field.subType.nested.path,
},
aggs: body.aggs,
},
},
};
}

return body;
}
1 change: 1 addition & 0 deletions src/plugins/data/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export {
IndexPatternsFetcher,
FieldDescriptor,
shouldReadFieldFromDocValues,
indexPatternsUtils,
} from './index_patterns';
export * from './search';
export {
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/server/index_patterns/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import * as indexPatternsUtils from './utils';

export { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher';
export { IndexPatternsService } from './index_patterns_service';
export { indexPatternsUtils };
Loading

0 comments on commit 1fcf01e

Please sign in to comment.