Cancel discarded KQL value suggestion requests (#51411)
* Fix filter matches index for filters with partial meta * Abort discarded KQL value suggestion requests * Abort server-side connection to ES * Fix failing test
This commit is contained in:
parent
4bbe3cf85b
commit
25c750b225
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { get, map } from 'lodash';
|
||||
import { abortableRequestHandler } from '../../../../../elasticsearch/lib/abortable_request_handler';
|
||||
|
||||
export function registerValueSuggestions(server) {
|
||||
const serverConfig = server.config();
|
||||
|
@ -26,7 +27,7 @@ export function registerValueSuggestions(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/suggestions/values/{index}',
|
||||
method: ['POST'],
|
||||
handler: async function (req) {
|
||||
handler: abortableRequestHandler(async function (signal, req) {
|
||||
const { index } = req.params;
|
||||
const { field: fieldName, query, boolFilter } = req.payload;
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
|
@ -46,7 +47,7 @@ export function registerValueSuggestions(server) {
|
|||
);
|
||||
|
||||
try {
|
||||
const response = await callWithRequest(req, 'search', { index, body });
|
||||
const response = await callWithRequest(req, 'search', { index, body }, { signal });
|
||||
const buckets = get(response, 'aggregations.suggestions.buckets')
|
||||
|| get(response, 'aggregations.nestedSuggestions.suggestions.buckets')
|
||||
|| [];
|
||||
|
@ -55,7 +56,7 @@ export function registerValueSuggestions(server) {
|
|||
} catch (error) {
|
||||
throw server.plugins.elasticsearch.handleESError(error);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ export type GetSuggestions = (args: {
|
|||
query: string;
|
||||
selectionStart: number;
|
||||
selectionEnd: number;
|
||||
signal?: AbortSignal;
|
||||
}) => Promise<AutocompleteSuggestion[]>;
|
||||
|
||||
/** @public **/
|
||||
|
|
|
@ -28,23 +28,36 @@ export function getSuggestionsProvider(
|
|||
http: HttpServiceBase
|
||||
): IGetSuggestions {
|
||||
const requestSuggestions = memoize(
|
||||
(index: string, field: IFieldType, query: string, boolFilter: any = []) => {
|
||||
(
|
||||
index: string,
|
||||
field: IFieldType,
|
||||
query: string,
|
||||
boolFilter: any = [],
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
return http.fetch(`/api/kibana/suggestions/values/${index}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query, field: field.name, boolFilter }),
|
||||
signal,
|
||||
});
|
||||
},
|
||||
resolver
|
||||
);
|
||||
|
||||
return async (index: string, field: IFieldType, query: string, boolFilter?: any) => {
|
||||
return async (
|
||||
index: string,
|
||||
field: IFieldType,
|
||||
query: string,
|
||||
boolFilter?: any,
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues');
|
||||
if (field.type === 'boolean') {
|
||||
return [true, false];
|
||||
} else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') {
|
||||
return [];
|
||||
}
|
||||
return await requestSuggestions(index, field, query, boolFilter);
|
||||
return await requestSuggestions(index, field, query, boolFilter, signal);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ export class QueryStringInputUI extends Component<Props, State> {
|
|||
public inputRef: HTMLInputElement | null = null;
|
||||
|
||||
private persistedLog: PersistedLog | undefined;
|
||||
private abortController: AbortController | undefined;
|
||||
private services = this.props.kibana.services;
|
||||
private componentIsUnmounting = false;
|
||||
|
||||
|
@ -163,12 +164,22 @@ export class QueryStringInputUI extends Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({
|
||||
query: queryString,
|
||||
selectionStart,
|
||||
selectionEnd,
|
||||
});
|
||||
return [...suggestions, ...recentSearchSuggestions];
|
||||
try {
|
||||
if (this.abortController) this.abortController.abort();
|
||||
this.abortController = new AbortController();
|
||||
const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({
|
||||
query: queryString,
|
||||
selectionStart,
|
||||
selectionEnd,
|
||||
signal: this.abortController.signal,
|
||||
});
|
||||
return [...suggestions, ...recentSearchSuggestions];
|
||||
} catch (e) {
|
||||
// TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error
|
||||
// Ignore aborted requests
|
||||
if (e.message === 'The user aborted a request.') return;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
private getRecentSearchSuggestions = (query: string) => {
|
||||
|
|
|
@ -22,7 +22,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => {
|
|||
return provider({ config, indexPatterns, boolFilter });
|
||||
});
|
||||
|
||||
return function getSuggestions({ query, selectionStart, selectionEnd }) {
|
||||
return function getSuggestions({ query, selectionStart, selectionEnd, signal }) {
|
||||
const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr(selectionEnd)}`;
|
||||
|
||||
let cursorNode;
|
||||
|
@ -34,7 +34,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => {
|
|||
|
||||
const { suggestionTypes = [] } = cursorNode;
|
||||
const suggestionsByType = suggestionTypes.map(type => {
|
||||
return getSuggestionsByType[type](cursorNode);
|
||||
return getSuggestionsByType[type](cursorNode, signal);
|
||||
});
|
||||
return Promise.all(suggestionsByType)
|
||||
.then(suggestionsByType => dedup(flatten(suggestionsByType)));
|
||||
|
|
|
@ -27,14 +27,14 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) {
|
|||
suffix,
|
||||
fieldName,
|
||||
nestedPath,
|
||||
}) {
|
||||
}, signal) {
|
||||
const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName;
|
||||
const fields = allFields.filter(field => field.name === fullFieldName);
|
||||
const query = `${prefix}${suffix}`.trim();
|
||||
const { getSuggestions } = npStart.plugins.data;
|
||||
|
||||
const suggestionsByField = fields.map(field => {
|
||||
return getSuggestions(field.indexPatternTitle, field, query, boolFilter).then(data => {
|
||||
return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then(data => {
|
||||
const quotedValues = data.map(value => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}`);
|
||||
return wrapAsSuggestions(start, end, query, quotedValues);
|
||||
});
|
||||
|
|
|
@ -112,7 +112,7 @@ describe('Kuery value suggestions', function () {
|
|||
const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions');
|
||||
await getSuggestions({ fieldName, prefix, suffix });
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined);
|
||||
expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined, undefined);
|
||||
});
|
||||
|
||||
test('should escape quotes in suggestions', async () => {
|
||||
|
|
Loading…
Reference in a new issue