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

[Data Plugin] combine autocomplete provider and suggestions provider #54451

Merged
merged 27 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cff60f8
[Data Plugin] combine autocomplete provider and suggestions provider
alexwizp Jan 10, 2020
76e902b
Merge remote-tracking branch 'upstream/master'
alexwizp Jan 10, 2020
ca2bd93
[Data Plugin] combine autocomplete provider and suggestions provider …
alexwizp Jan 10, 2020
595b26d
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 13, 2020
eb8250f
autocomplete_provider -> autocomplete
alexwizp Jan 13, 2020
6ee7644
value_suggestions.ts - change getSuggestions method
alexwizp Jan 13, 2020
336de38
remove suggestions_provider folder
alexwizp Jan 13, 2020
b90493b
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 13, 2020
3d6bac1
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 14, 2020
8e2d6ae
fix PR comments
alexwizp Jan 14, 2020
7862921
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 14, 2020
5ca7929
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 15, 2020
ffc07ac
fix PR comments
alexwizp Jan 15, 2020
7c4976d
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 15, 2020
403e5da
fix CI
alexwizp Jan 15, 2020
6b0ae89
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 15, 2020
b7b0e90
Merge branch 'master' into 52843
elasticmachine Jan 16, 2020
0fbaf16
fix CI
alexwizp Jan 16, 2020
0293cb2
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 16, 2020
2179521
Merge branch 'master' into 52843
elasticmachine Jan 16, 2020
165fa68
Merge branch 'master' into 52843
elasticmachine Jan 16, 2020
7b3875b
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 17, 2020
de397d3
getFieldSuggestions -> getValueSuggestions
alexwizp Jan 17, 2020
e91aa20
Merge remote-tracking branch 'origin/52843' into 52843
alexwizp Jan 17, 2020
e2c8d2f
Merge remote-tracking branch 'upstream/master' into 52843
alexwizp Jan 17, 2020
5bc1213
update Jest snaphots
alexwizp Jan 17, 2020
2024209
Merge branch 'master' into 52843
elasticmachine Jan 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions src/plugins/data/public/autocomplete/autocomplete_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 } from 'src/core/public';
import { QuerySuggestionsGetFn } from './providers/query_suggestion_provider';
import {
setupValueSuggestionProvider,
ValueSuggestionsGetFn,
} from './providers/value_suggestion_provider';

export class AutocompleteService {
private readonly querySuggestionProviders: Map<string, QuerySuggestionsGetFn> = new Map();
private getValueSuggestions?: ValueSuggestionsGetFn;

private addQuerySuggestionProvider = (
language: string,
provider: QuerySuggestionsGetFn
): void => {
if (language && provider) {
this.querySuggestionProviders.set(language, provider);
}
};

private getQuerySuggestions: QuerySuggestionsGetFn = args => {
const { language } = args;
const provider = this.querySuggestionProviders.get(language);

if (provider) {
return provider(args);
}
};

private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language);

/** @public **/
public setup(core: CoreSetup) {
this.getValueSuggestions = setupValueSuggestionProvider(core);

return {
addQuerySuggestionProvider: this.addQuerySuggestionProvider,

/** @obsolete **/
/** please use "getProvider" only from the start contract **/
getQuerySuggestions: this.getQuerySuggestions,
};
}

/** @public **/
public start() {
return {
getQuerySuggestions: this.getQuerySuggestions,
hasQuerySuggestions: this.hasQuerySuggestions,
getValueSuggestions: this.getValueSuggestions!,
};
}

/** @internal **/
public clearProviders(): void {
this.querySuggestionProviders.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
* under the License.
*/

export { getSuggestionsProvider } from './value_suggestions';
export { AutocompleteService } from './autocomplete_service';
export { QuerySuggestion, QuerySuggestionType, QuerySuggestionsGetFn } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,40 @@
* under the License.
*/

import { AutocompleteProviderRegister } from '.';
import { IIndexPattern, IFieldType } from '../../common';
import { IFieldType, IIndexPattern } from '../../../common/index_patterns';

export type AutocompletePublicPluginSetup = Pick<
AutocompleteProviderRegister,
'addProvider' | 'getProvider'
>;
export type AutocompletePublicPluginStart = Pick<AutocompleteProviderRegister, 'getProvider'>;
export type QuerySuggestionType = 'field' | 'value' | 'operator' | 'conjunction' | 'recentSearch';

/** @public **/
export type AutocompleteProvider = (args: {
config: {
get(configKey: string): any;
};
indexPatterns: IIndexPattern[];
boolFilter?: any;
}) => GetSuggestions;
export type QuerySuggestionsGetFn = (
args: QuerySuggestionsGetFnArgs
) => Promise<QuerySuggestion[]> | undefined;

/** @public **/
export type GetSuggestions = (args: {
interface QuerySuggestionsGetFnArgs {
language: string;
indexPatterns: IIndexPattern[];
query: string;
selectionStart: number;
selectionEnd: number;
signal?: AbortSignal;
}) => Promise<AutocompleteSuggestion[]>;

/** @public **/
export type AutocompleteSuggestionType =
| 'field'
| 'value'
| 'operator'
| 'conjunction'
| 'recentSearch';

// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm
// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the
// TypeScript compiler will narrow the type to the parts of the union that have a field prop.
/** @public **/
export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion;
boolFilter?: any;
lizozom marked this conversation as resolved.
Show resolved Hide resolved
}

interface BasicAutocompleteSuggestion {
interface BasicQuerySuggestion {
type: QuerySuggestionType;
description?: string;
end: number;
start: number;
text: string;
type: AutocompleteSuggestionType;
cursorIndex?: number;
}

export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & {
interface FieldQuerySuggestion extends BasicQuerySuggestion {
type: 'field';
field: IFieldType;
};
}

// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm
// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the
// TypeScript compiler will narrow the type to the parts of the union that have a field prop.
/** @public **/
export type QuerySuggestion = BasicQuerySuggestion | FieldQuerySuggestion;
Original file line number Diff line number Diff line change
Expand Up @@ -17,98 +17,121 @@
* under the License.
*/

import { stubIndexPattern, stubFields } from '../stubs';
import { getSuggestionsProvider } from './value_suggestions';
import { IUiSettingsClient } from 'kibana/public';
import { stubIndexPattern, stubFields } from '../../stubs';
import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider';
import { IUiSettingsClient, CoreSetup } from 'kibana/public';

describe('getSuggestions', () => {
let getSuggestions: any;
describe('FieldSuggestions', () => {
let getValueSuggestions: ValueSuggestionsGetFn;
let http: any;
let shouldSuggestValues: boolean;

describe('with value suggestions disabled', () => {
beforeEach(() => {
const config = { get: (key: string) => false } as IUiSettingsClient;
http = { fetch: jest.fn() };
getSuggestions = getSuggestionsProvider(config, http);
});
beforeEach(() => {
const uiSettings = { get: (key: string) => shouldSuggestValues } as IUiSettingsClient;
http = { fetch: jest.fn() };

getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup);
});

describe('with value suggestions disabled', () => {
it('should return an empty array', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields;
const query = '';
const suggestions = await getSuggestions(index, field, query);
const suggestions = await getValueSuggestions({
indexPattern: stubIndexPattern,
field: stubFields[0],
query: '',
});

expect(suggestions).toEqual([]);
expect(http.fetch).not.toHaveBeenCalled();
});
});

describe('with value suggestions enabled', () => {
beforeEach(() => {
const config = { get: (key: string) => true } as IUiSettingsClient;
http = { fetch: jest.fn() };
getSuggestions = getSuggestionsProvider(config, http);
});
shouldSuggestValues = true;

it('should return true/false for boolean fields', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(({ type }) => type === 'boolean');
const query = '';
const suggestions = await getSuggestions(index, field, query);
const suggestions = await getValueSuggestions({
indexPattern: stubIndexPattern,
field,
query: '',
});

expect(suggestions).toEqual([true, false]);
expect(http.fetch).not.toHaveBeenCalled();
});

it('should return an empty array if the field type is not a string or boolean', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(({ type }) => type !== 'string' && type !== 'boolean');
const query = '';
const suggestions = await getSuggestions(index, field, query);
const suggestions = await getValueSuggestions({
indexPattern: stubIndexPattern,
field,
query: '',
});

expect(suggestions).toEqual([]);
expect(http.fetch).not.toHaveBeenCalled();
});

it('should return an empty array if the field is not aggregatable', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(({ aggregatable }) => !aggregatable);
const query = '';
const suggestions = await getSuggestions(index, field, query);
const suggestions = await getValueSuggestions({
indexPattern: stubIndexPattern,
field,
query: '',
});

expect(suggestions).toEqual([]);
expect(http.fetch).not.toHaveBeenCalled();
});

it('should otherwise request suggestions', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
await getSuggestions(index, field, query);

await getValueSuggestions({
indexPattern: stubIndexPattern,
field,
query: '',
});

expect(http.fetch).toHaveBeenCalled();
});

it('should cache results if using the same index/field/query/filter', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
await getSuggestions(index, field, query);
await getSuggestions(index, field, query);
const args = {
indexPattern: stubIndexPattern,
field,
query: '',
};

await getValueSuggestions(args);
await getValueSuggestions(args);

expect(http.fetch).toHaveBeenCalledTimes(1);
});

it('should cache results for only one minute', async () => {
const index = stubIndexPattern.id;
const [field] = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
const query = '';
const args = {
indexPattern: stubIndexPattern,
field,
query: '',
};

const { now } = Date;
Date.now = jest.fn(() => 0);
await getSuggestions(index, field, query);

await getValueSuggestions(args);

Date.now = jest.fn(() => 60 * 1000);
await getSuggestions(index, field, query);
await getValueSuggestions(args);
Date.now = now;

expect(http.fetch).toHaveBeenCalledTimes(2);
Expand All @@ -118,14 +141,54 @@ describe('getSuggestions', () => {
const fields = stubFields.filter(
({ type, aggregatable }) => type === 'string' && aggregatable
);
await getSuggestions('index', fields[0], '');
await getSuggestions('index', fields[0], 'query');
await getSuggestions('index', fields[1], '');
await getSuggestions('index', fields[1], 'query');
await getSuggestions('logstash-*', fields[0], '');
await getSuggestions('logstash-*', fields[0], 'query');
await getSuggestions('logstash-*', fields[1], '');
await getSuggestions('logstash-*', fields[1], 'query');

await getValueSuggestions({
indexPattern: stubIndexPattern,
field: fields[0],
query: '',
});
await getValueSuggestions({
indexPattern: stubIndexPattern,
field: fields[0],
query: 'query',
});
await getValueSuggestions({
indexPattern: stubIndexPattern,
field: fields[1],
query: '',
});
await getValueSuggestions({
indexPattern: stubIndexPattern,
field: fields[1],
query: 'query',
});

const customIndexPattern = {
...stubIndexPattern,
title: 'customIndexPattern',
};

await getValueSuggestions({
indexPattern: customIndexPattern,
field: fields[0],
query: '',
});
await getValueSuggestions({
indexPattern: customIndexPattern,
field: fields[0],
query: 'query',
});
await getValueSuggestions({
indexPattern: customIndexPattern,
field: fields[1],
query: '',
});
await getValueSuggestions({
indexPattern: customIndexPattern,
field: fields[1],
query: 'query',
});

expect(http.fetch).toHaveBeenCalledTimes(8);
});
});
Expand Down
Loading