Extract KQL autocomplete to a plugin (#20747)

* fix: move autocomplete to x-pack basic

* fix: apm support

* fix: renames

* [uiExports] switch to new autocompleteProviders export type

* fix: remove unnecessary stuff from the plugin spec
This commit is contained in:
Lukas Olson 2018-07-24 12:33:15 -07:00 committed by GitHub
parent a56c856e0d
commit ecc0f5e6e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 161 additions and 243 deletions

View file

@ -42,6 +42,7 @@ import 'uiExports/docViews';
import 'uiExports/embeddableFactories';
import 'uiExports/inspectorViews';
import 'uiExports/search';
import 'uiExports/autocompleteProviders';
import 'ui/autoload/all';
import './home';

View file

@ -17,22 +17,12 @@
* under the License.
*/
export function escapeQuotes(string) {
return string.replace(/"/g, '\\"');
const autocompleteProviders = new Map();
export function addAutocompleteProvider(language, provider) {
autocompleteProviders.set(language, provider);
}
export const escapeKuery = (string) => escapeNot(escapeAndOr(escapeSpecialCharacters(string)));
// See the SpecialCharacter rule in kuery.peg
function escapeSpecialCharacters(string) {
return string.replace(/[\\():<>"*]/g, '\\$&'); // $& means the whole matched string
}
// See the Keyword rule in kuery.peg
function escapeAndOr(string) {
return string.replace(/(\s+)(and|or)(\s+)/ig, '$1\\$2$3');
}
function escapeNot(string) {
return string.replace(/not(\s+)/ig, '\\$&');
export function getAutocompleteProvider(language) {
return autocompleteProviders.get(language);
}

View file

@ -20,4 +20,3 @@
export * from './ast';
export * from './filter_migration';
export * from './node_types';
export * from './suggestions';

View file

@ -1,41 +0,0 @@
/*
* 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.
*/
const type = 'conjunction';
const conjunctions = {
and: `<p>Requires <span class="suggestionItem__callout">both arguments</span> to be true</p>`,
or: `<p>Requires <span class="suggestionItem__callout">one or more arguments</span> to be true</p>`
};
function getDescription(conjunction) {
return conjunctions[conjunction];
}
export function getSuggestionsProvider() {
return function getConjunctionSuggestions({ text, end }) {
if (!text.endsWith(' ')) return [];
const suggestions = Object.keys(conjunctions).map(conjunction => {
const text = `${conjunction} `;
const description = getDescription(conjunction);
return { type, text, description, start: end, end };
});
return suggestions;
};
}

View file

@ -17,12 +17,12 @@
* under the License.
*/
import { compact, get } from 'lodash';
import { compact } from 'lodash';
import { uiModules } from '../../modules';
import { callAfterBindingsWorkaround } from '../../compat';
import template from './query_bar.html';
import suggestionTemplate from './suggestion.html';
import { getSuggestionsProvider } from '../../kuery';
import { getAutocompleteProvider } from '../../autocomplete_providers';
import './suggestion.less';
import '../../directives/match_pairs';
import './query_popover';
@ -75,18 +75,25 @@ module.directive('queryBar', function () {
}
};
this.updateSuggestions = () => {
const query = get(this, 'localQuery.query');
if (typeof query === 'undefined') return;
this.updateSuggestions = async () => {
const suggestions = await this.getSuggestions();
$scope.$apply(() => this.suggestions = suggestions);
};
this.suggestions = this.getRecentSearchSuggestions(query);
if (this.localQuery.language !== 'kuery' || !this.getKuerySuggestions) return;
this.getSuggestions = async () => {
const { localQuery: { query, language } } = this;
const recentSearchSuggestions = this.getRecentSearchSuggestions(query);
const autocompleteProvider = getAutocompleteProvider(language);
if (!autocompleteProvider) return recentSearchSuggestions;
const legacyIndexPatterns = await this.getIndexPatterns();
const indexPatterns = getFromLegacyIndexPattern(legacyIndexPatterns);
const getAutocompleteSuggestions = autocompleteProvider({ config, indexPatterns });
const { selectionStart, selectionEnd } = $element.find('input')[0];
this.getKuerySuggestions({ query, selectionStart, selectionEnd })
.then(suggestions => {
$scope.$apply(() => this.suggestions = [...suggestions, ...this.suggestions]);
});
const suggestions = await getAutocompleteSuggestions({ query, selectionStart, selectionEnd });
return [...suggestions, ...recentSearchSuggestions];
};
// TODO: Figure out a better way to set selection
@ -107,6 +114,7 @@ module.directive('queryBar', function () {
};
this.getRecentSearchSuggestions = (query) => {
if (!this.persistedLog) return [];
const recentSearches = this.persistedLog.get();
const matchingRecentSearches = recentSearches.filter(search => search.includes(query));
return matchingRecentSearches.map(recentSearch => {
@ -133,14 +141,7 @@ module.directive('queryBar', function () {
}, true);
$scope.$watch('queryBar.indexPatterns', () => {
this.getIndexPatterns().then(indexPatterns => {
this.getKuerySuggestions = getSuggestionsProvider({
config,
indexPatterns: getFromLegacyIndexPattern(indexPatterns)
});
this.updateSuggestions();
});
this.updateSuggestions();
});
})
};

View file

@ -53,6 +53,7 @@ export {
aliases,
visualize,
search,
autocompleteProviders,
} from './ui_app_extensions';
export {

View file

@ -37,6 +37,7 @@ export const visTypes = appExtension;
export const visResponseHandlers = appExtension;
export const visRequestHandlers = appExtension;
export const visEditorTypes = appExtension;
export const autocompleteProviders = appExtension;
export const savedObjectTypes = appExtension;
export const embeddableFactories = appExtension;
export const dashboardPanelActions = appExtension;

View file

@ -22,6 +22,7 @@ import { cloud } from './plugins/cloud';
import { indexManagement } from './plugins/index_management';
import { consoleExtensions } from './plugins/console_extensions';
import { notifications } from './plugins/notifications';
import { kueryAutocomplete } from './plugins/kuery_autocomplete';
module.exports = function (kibana) {
return [
@ -43,5 +44,6 @@ module.exports = function (kibana) {
indexManagement(kibana),
consoleExtensions(kibana),
notifications(kibana),
kueryAutocomplete(kibana)
];
};

View file

@ -44,6 +44,7 @@
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "3.3.1",
"expect.js": "0.3.1",
"fetch-mock": "^5.13.1",
"gulp": "3.9.1",
"gulp-load-plugins": "1.2.0",
"gulp-mocha": "2.2.0",

View file

@ -4,11 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
fromKueryExpression,
toElasticsearchQuery,
getSuggestionsProvider
} from 'ui/kuery';
import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery';
import { getAutocompleteProvider } from 'ui/autocomplete_providers';
export function convertKueryToEsQuery(kuery, indexPattern) {
const ast = fromKueryExpression(kuery);
@ -21,16 +18,18 @@ export async function getSuggestions(
apmIndexPattern,
boolFilter
) {
const autocompleteProvider = getAutocompleteProvider('kuery');
if (!autocompleteProvider) return [];
const config = {
get: () => true
};
const getKuerySuggestions = getSuggestionsProvider({
const getAutocompleteSuggestions = autocompleteProvider({
config,
indexPatterns: [apmIndexPattern],
boolFilter
});
return getKuerySuggestions({
return getAutocompleteSuggestions({
query,
selectionStart,
selectionEnd: selectionStart

View file

@ -25,7 +25,7 @@ import 'uiExports/navbarExtensions';
import 'uiExports/docViews';
import 'uiExports/fieldFormats';
import 'uiExports/search';
import 'uiExports/autocompleteProviders';
import _ from 'lodash';
import 'ui/autoload/all';
import 'plugins/kibana/dashboard';

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { resolve } from 'path';
export const kueryAutocomplete = (kibana) => new kibana.Plugin({
id: 'kuery_autocomplete',
publicDir: resolve(__dirname, 'public'),
uiExports: {
autocompleteProviders: [
'plugins/kuery_autocomplete/autocomplete_providers'
],
}
});

View file

@ -1,20 +1,7 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';

View file

@ -1,20 +1,7 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';

View file

@ -1,26 +1,13 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import { getSuggestionsProvider } from '../field';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import { isFilterable } from '../../../index_patterns/static_utils';
import indexPatternResponse from 'ui/kuery/__tests__/index_pattern_response.json';
import { isFilterable } from 'ui/index_patterns/static_utils';
describe('Kuery field suggestions', function () {
let indexPattern;

View file

@ -1,25 +1,12 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import { getSuggestionsProvider } from '../operator';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import indexPatternResponse from 'ui/kuery/__tests__/index_pattern_response.json';
describe('Kuery operator suggestions', function () {
let indexPatterns;

View file

@ -1,27 +1,14 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import sinon from 'sinon';
import fetchMock from 'fetch-mock';
import { getSuggestionsProvider } from '../value';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import indexPatternResponse from 'ui/kuery/__tests__/index_pattern_response.json';
describe('Kuery value suggestions', function () {
let config;

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
const type = 'conjunction';
const conjunctions = {
and: `<p>Requires <span class="suggestionItem__callout">both arguments</span> to be true</p>`,
or: `<p>Requires <span class="suggestionItem__callout">one or more arguments</span> to be true</p>`
};
function getDescription(conjunction) {
return conjunctions[conjunction];
}
export function getSuggestionsProvider() {
return function getConjunctionSuggestions({ text, end }) {
if (!text.endsWith(' ')) return [];
const suggestions = Object.keys(conjunctions).map(conjunction => {
const text = `${conjunction} `;
const description = getDescription(conjunction);
return { type, text, description, start: end, end };
});
return suggestions;
};
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export function escapeQuotes(string) {
return string.replace(/"/g, '\\"');
}
export const escapeKuery = (string) => escapeNot(escapeAndOr(escapeSpecialCharacters(string)));
// See the SpecialCharacter rule in kuery.peg
function escapeSpecialCharacters(string) {
return string.replace(/[\\():<>"*]/g, '\\$&'); // $& means the whole matched string
}
// See the Keyword rule in kuery.peg
function escapeAndOr(string) {
return string.replace(/(\s+)(and|or)(\s+)/ig, '$1\\$2$3');
}
function escapeNot(string) {
return string.replace(/not(\s+)/ig, '\\$&');
}

View file

@ -1,26 +1,13 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { escape, flatten } from 'lodash';
import { escapeKuery } from './escape_kuery';
import { sortPrefixFirst } from '../../utils/sort_prefix_first';
import { isFilterable } from '../../index_patterns/static_utils';
import { sortPrefixFirst } from 'ui/utils/sort_prefix_first';
import { isFilterable } from 'ui/index_patterns/static_utils';
const type = 'field';

View file

@ -1,32 +1,20 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { flatten, mapValues, uniq } from 'lodash';
import { fromKueryExpression } from '../ast';
import { fromKueryExpression } from 'ui/kuery/ast';
import { getSuggestionsProvider as field } from './field';
import { getSuggestionsProvider as value } from './value';
import { getSuggestionsProvider as operator } from './operator';
import { getSuggestionsProvider as conjunction } from './conjunction';
import { addAutocompleteProvider } from 'ui/autocomplete_providers';
const cursorSymbol = '@kuery-cursor@';
export function getSuggestionsProvider({ config, indexPatterns, boolFilter }) {
addAutocompleteProvider('kuery', ({ config, indexPatterns, boolFilter }) => {
const getSuggestionsByType = mapValues({ field, value, operator, conjunction }, provider => {
return provider({ config, indexPatterns, boolFilter });
});
@ -48,7 +36,7 @@ export function getSuggestionsProvider({ config, indexPatterns, boolFilter }) {
return Promise.all(suggestionsByType)
.then(suggestionsByType => dedup(flatten(suggestionsByType)));
};
}
});
function dedup(suggestions) {
return uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|'));

View file

@ -1,20 +1,7 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { flatten } from 'lodash';

View file

@ -1,26 +1,13 @@
/*
* 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.
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import 'isomorphic-fetch';
import { flatten, memoize } from 'lodash';
import { escapeQuotes } from './escape_kuery';
import { kfetch } from '../../kfetch';
import { kfetch } from 'ui/kfetch';
const type = 'value';

View file

@ -2692,6 +2692,14 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
fetch-mock@^5.13.1:
version "5.13.1"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7"
dependencies:
glob-to-regexp "^0.3.0"
node-fetch "^1.3.3"
path-to-regexp "^1.7.0"
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@ -3084,6 +3092,10 @@ glob-stream@^6.1.0:
to-absolute-glob "^2.0.0"
unique-stream "^2.0.2"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob-watcher@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@ -5520,7 +5532,7 @@ nise@^1.2.0:
path-to-regexp "^1.7.0"
text-encoding "^0.6.4"
node-fetch@^1.0.1:
node-fetch@^1.0.1, node-fetch@^1.3.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
dependencies:
@ -8102,9 +8114,6 @@ to-object-path@^0.3.0:
to-regex-range@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
dependencies:
is-number "^3.0.0"
repeat-string "^1.6.1"
to-regex@^3.0.1:
version "3.0.1"