mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
Update QueryBarInput to accept index pattern strings (#36916)
For many use cases a consumer of the QueryBarInput (or QueryBar) might only have an index pattern string in hand. Instead of forcing every consumer to reimplement the fetching logic to get a full pattern object, this PR updates the QueryBarInput to do the fetching itself if the indexPatterns array prop contains any strings. If a string does not exactly match the title of any of the saved objects then we return the default index pattern instead.
This commit is contained in:
parent
ec1dc71c37
commit
b7b7aa504f
8 changed files with 142 additions and 11 deletions
|
@ -18,3 +18,4 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { QueryBar } from './query_bar';
|
export { QueryBar } from './query_bar';
|
||||||
|
export { QueryBarInput } from './query_bar_input';
|
||||||
|
|
|
@ -59,7 +59,7 @@ interface Props {
|
||||||
disableAutoFocus?: boolean;
|
disableAutoFocus?: boolean;
|
||||||
appName: string;
|
appName: string;
|
||||||
screenTitle: string;
|
screenTitle: string;
|
||||||
indexPatterns: IndexPattern[];
|
indexPatterns: Array<IndexPattern | string>;
|
||||||
store: Storage;
|
store: Storage;
|
||||||
intl: InjectedIntl;
|
intl: InjectedIntl;
|
||||||
prepend?: any;
|
prepend?: any;
|
||||||
|
|
|
@ -20,6 +20,21 @@
|
||||||
import { createKfetch } from 'ui/kfetch/kfetch';
|
import { createKfetch } from 'ui/kfetch/kfetch';
|
||||||
import { setup } from 'test_utils/http_test_setup';
|
import { setup } from 'test_utils/http_test_setup';
|
||||||
|
|
||||||
|
const mockIndexPattern = {
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'response',
|
||||||
|
type: 'number',
|
||||||
|
esTypes: ['integer'],
|
||||||
|
aggregatable: true,
|
||||||
|
filterable: true,
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const mockChromeFactory = jest.fn(() => {
|
const mockChromeFactory = jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
getBasePath: () => `foo`,
|
getBasePath: () => `foo`,
|
||||||
|
@ -52,6 +67,10 @@ const mockAutocompleteProvider = jest.fn(() => mockGetAutocompleteSuggestions);
|
||||||
export const mockGetAutocompleteProvider = jest.fn(() => mockAutocompleteProvider);
|
export const mockGetAutocompleteProvider = jest.fn(() => mockAutocompleteProvider);
|
||||||
const mockKfetch = jest.fn(() => createKfetch(setup().http));
|
const mockKfetch = jest.fn(() => createKfetch(setup().http));
|
||||||
|
|
||||||
|
export const mockFetchIndexPatterns = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve([mockIndexPattern]));
|
||||||
|
|
||||||
jest.mock('ui/chrome', () => mockChromeFactory());
|
jest.mock('ui/chrome', () => mockChromeFactory());
|
||||||
jest.mock('ui/kfetch', () => ({
|
jest.mock('ui/kfetch', () => ({
|
||||||
kfetch: () => {},
|
kfetch: () => {},
|
||||||
|
@ -72,6 +91,10 @@ jest.mock('ui/kfetch', () => ({
|
||||||
kfetch: mockKfetch,
|
kfetch: mockKfetch,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../lib/fetch_index_patterns', () => ({
|
||||||
|
fetchIndexPatterns: mockFetchIndexPatterns,
|
||||||
|
}));
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
// Using doMock to avoid hoisting so that I can override only the debounce method in lodash
|
// Using doMock to avoid hoisting so that I can override only the debounce method in lodash
|
||||||
jest.doMock('lodash', () => ({
|
jest.doMock('lodash', () => ({
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
mockFetchIndexPatterns,
|
||||||
mockGetAutocompleteProvider,
|
mockGetAutocompleteProvider,
|
||||||
mockGetAutocompleteSuggestions,
|
mockGetAutocompleteSuggestions,
|
||||||
mockPersistedLog,
|
mockPersistedLog,
|
||||||
|
@ -131,6 +132,8 @@ describe('QueryBarInput', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should create a unique PersistedLog based on the appName and query language', () => {
|
it('Should create a unique PersistedLog based on the appName and query language', () => {
|
||||||
|
mockPersistedLogFactory.mockClear();
|
||||||
|
|
||||||
mountWithIntl(
|
mountWithIntl(
|
||||||
<QueryBarInput.WrappedComponent
|
<QueryBarInput.WrappedComponent
|
||||||
query={kqlQuery}
|
query={kqlQuery}
|
||||||
|
@ -240,4 +243,23 @@ describe('QueryBarInput', () => {
|
||||||
expect(mockGetAutocompleteProvider).toHaveBeenCalledWith('kuery');
|
expect(mockGetAutocompleteProvider).toHaveBeenCalledWith('kuery');
|
||||||
expect(mockGetAutocompleteSuggestions).toHaveBeenCalled();
|
expect(mockGetAutocompleteSuggestions).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should accept index pattern strings and fetch the full object', () => {
|
||||||
|
mockFetchIndexPatterns.mockClear();
|
||||||
|
|
||||||
|
mountWithIntl(
|
||||||
|
<QueryBarInput.WrappedComponent
|
||||||
|
query={kqlQuery}
|
||||||
|
onSubmit={noop}
|
||||||
|
appName={'discover'}
|
||||||
|
screenTitle={'Another Screen'}
|
||||||
|
indexPatterns={['logstash-*']}
|
||||||
|
store={createMockStorage()}
|
||||||
|
disableAutoFocus={true}
|
||||||
|
intl={null as any}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockFetchIndexPatterns).toHaveBeenCalledWith(['logstash-*']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,8 +28,8 @@ import {
|
||||||
AutocompleteSuggestionType,
|
AutocompleteSuggestionType,
|
||||||
getAutocompleteProvider,
|
getAutocompleteProvider,
|
||||||
} from 'ui/autocomplete_providers';
|
} from 'ui/autocomplete_providers';
|
||||||
import { debounce, compact } from 'lodash';
|
import { debounce, compact, isEqual } from 'lodash';
|
||||||
import { IndexPattern } from 'ui/index_patterns';
|
import { IndexPattern, StaticIndexPattern } from 'ui/index_patterns';
|
||||||
import { PersistedLog } from 'ui/persisted_log';
|
import { PersistedLog } from 'ui/persisted_log';
|
||||||
import chrome from 'ui/chrome';
|
import chrome from 'ui/chrome';
|
||||||
import { kfetch } from 'ui/kfetch';
|
import { kfetch } from 'ui/kfetch';
|
||||||
|
@ -38,6 +38,7 @@ import { fromUser, matchPairs, toUser } from '../lib';
|
||||||
import { QueryLanguageSwitcher } from './language_switcher';
|
import { QueryLanguageSwitcher } from './language_switcher';
|
||||||
import { SuggestionsComponent } from './typeahead/suggestions_component';
|
import { SuggestionsComponent } from './typeahead/suggestions_component';
|
||||||
import { getQueryLog } from '../lib/get_query_log';
|
import { getQueryLog } from '../lib/get_query_log';
|
||||||
|
import { fetchIndexPatterns } from '../lib/fetch_index_patterns';
|
||||||
|
|
||||||
interface Query {
|
interface Query {
|
||||||
query: string;
|
query: string;
|
||||||
|
@ -45,7 +46,7 @@ interface Query {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
indexPatterns: IndexPattern[];
|
indexPatterns: Array<IndexPattern | string>;
|
||||||
intl: InjectedIntl;
|
intl: InjectedIntl;
|
||||||
query: Query;
|
query: Query;
|
||||||
appName: string;
|
appName: string;
|
||||||
|
@ -65,6 +66,7 @@ interface State {
|
||||||
suggestionLimit: number;
|
suggestionLimit: number;
|
||||||
selectionStart: number | null;
|
selectionStart: number | null;
|
||||||
selectionEnd: number | null;
|
selectionEnd: number | null;
|
||||||
|
indexPatterns: StaticIndexPattern[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const KEY_CODES = {
|
const KEY_CODES = {
|
||||||
|
@ -90,6 +92,7 @@ export class QueryBarInputUI extends Component<Props, State> {
|
||||||
suggestionLimit: 50,
|
suggestionLimit: 50,
|
||||||
selectionStart: null,
|
selectionStart: null,
|
||||||
selectionEnd: null,
|
selectionEnd: null,
|
||||||
|
indexPatterns: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
public inputRef: HTMLInputElement | null = null;
|
public inputRef: HTMLInputElement | null = null;
|
||||||
|
@ -101,6 +104,21 @@ export class QueryBarInputUI extends Component<Props, State> {
|
||||||
return toUser(this.props.query.query);
|
return toUser(this.props.query.query);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private fetchIndexPatterns = async () => {
|
||||||
|
const stringPatterns = this.props.indexPatterns.filter(
|
||||||
|
indexPattern => typeof indexPattern === 'string'
|
||||||
|
) as string[];
|
||||||
|
const objectPatterns = this.props.indexPatterns.filter(
|
||||||
|
indexPattern => typeof indexPattern !== 'string'
|
||||||
|
) as IndexPattern[];
|
||||||
|
|
||||||
|
const objectPatternsFromStrings = await fetchIndexPatterns(stringPatterns);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
indexPatterns: [...objectPatterns, ...objectPatternsFromStrings],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private getSuggestions = async () => {
|
private getSuggestions = async () => {
|
||||||
if (!this.inputRef) {
|
if (!this.inputRef) {
|
||||||
return;
|
return;
|
||||||
|
@ -114,13 +132,13 @@ export class QueryBarInputUI extends Component<Props, State> {
|
||||||
const autocompleteProvider = getAutocompleteProvider(language);
|
const autocompleteProvider = getAutocompleteProvider(language);
|
||||||
if (
|
if (
|
||||||
!autocompleteProvider ||
|
!autocompleteProvider ||
|
||||||
!Array.isArray(this.props.indexPatterns) ||
|
!Array.isArray(this.state.indexPatterns) ||
|
||||||
compact(this.props.indexPatterns).length === 0
|
compact(this.state.indexPatterns).length === 0
|
||||||
) {
|
) {
|
||||||
return recentSearchSuggestions;
|
return recentSearchSuggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexPatterns = this.props.indexPatterns;
|
const indexPatterns = this.state.indexPatterns;
|
||||||
const getAutocompleteSuggestions = autocompleteProvider({ config, indexPatterns });
|
const getAutocompleteSuggestions = autocompleteProvider({ config, indexPatterns });
|
||||||
|
|
||||||
const { selectionStart, selectionEnd } = this.inputRef;
|
const { selectionStart, selectionEnd } = this.inputRef;
|
||||||
|
@ -368,14 +386,20 @@ export class QueryBarInputUI extends Component<Props, State> {
|
||||||
this.persistedLog = this.props.persistedLog
|
this.persistedLog = this.props.persistedLog
|
||||||
? this.props.persistedLog
|
? this.props.persistedLog
|
||||||
: getQueryLog(this.props.appName, this.props.query.language);
|
: getQueryLog(this.props.appName, this.props.query.language);
|
||||||
this.updateSuggestions();
|
|
||||||
|
this.fetchIndexPatterns().then(this.updateSuggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Props) {
|
public componentDidUpdate(prevProps: Props) {
|
||||||
this.persistedLog = this.props.persistedLog
|
this.persistedLog = this.props.persistedLog
|
||||||
? this.props.persistedLog
|
? this.props.persistedLog
|
||||||
: getQueryLog(this.props.appName, this.props.query.language);
|
: getQueryLog(this.props.appName, this.props.query.language);
|
||||||
|
|
||||||
|
if (!isEqual(prevProps.indexPatterns, this.props.indexPatterns)) {
|
||||||
|
this.fetchIndexPatterns().then(this.updateSuggestions);
|
||||||
|
} else {
|
||||||
this.updateSuggestions();
|
this.updateSuggestions();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.selectionStart !== null && this.state.selectionEnd !== null) {
|
if (this.state.selectionStart !== null && this.state.selectionEnd !== null) {
|
||||||
if (this.inputRef) {
|
if (this.inputRef) {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { QueryBar } from './components';
|
export { QueryBar, QueryBarInput } from './components';
|
||||||
export { fromUser } from './lib/from_user';
|
export { fromUser } from './lib/from_user';
|
||||||
export { toUser } from './lib/to_user';
|
export { toUser } from './lib/to_user';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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 chrome from 'ui/chrome';
|
||||||
|
import { getFromSavedObject } from 'ui/index_patterns/static_utils';
|
||||||
|
|
||||||
|
const config = chrome.getUiSettingsClient();
|
||||||
|
|
||||||
|
export async function fetchIndexPatterns(indexPatternStrings: string[]) {
|
||||||
|
const quotedIndexPatternStrings = indexPatternStrings.map(
|
||||||
|
indexPatternString => `"${indexPatternString}"`
|
||||||
|
);
|
||||||
|
const searchString = quotedIndexPatternStrings.join(' | ');
|
||||||
|
const indexPatternsFromSavedObjects = await chrome.getSavedObjectsClient().find({
|
||||||
|
type: 'index-pattern',
|
||||||
|
fields: ['title', 'fields'],
|
||||||
|
search: `"${searchString}"`,
|
||||||
|
searchFields: ['title'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const exactMatches = indexPatternsFromSavedObjects.savedObjects.filter(savedObject => {
|
||||||
|
return indexPatternStrings.includes(savedObject.attributes.title as string);
|
||||||
|
});
|
||||||
|
|
||||||
|
const allMatches =
|
||||||
|
exactMatches.length === indexPatternStrings.length
|
||||||
|
? exactMatches
|
||||||
|
: [...exactMatches, await fetchDefaultIndexPattern()];
|
||||||
|
|
||||||
|
return allMatches.map(getFromSavedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchDefaultIndexPattern = async () => {
|
||||||
|
const savedObjectsClient = chrome.getSavedObjectsClient();
|
||||||
|
const indexPattern = await savedObjectsClient.get('index-pattern', config.get('defaultIndex'));
|
||||||
|
|
||||||
|
return getFromSavedObject(indexPattern);
|
||||||
|
};
|
|
@ -18,7 +18,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { once } from 'lodash';
|
import { once } from 'lodash';
|
||||||
import { QueryBar, fromUser, toUser, setupDirective as setupQueryBarDirective } from './query_bar';
|
import {
|
||||||
|
QueryBar,
|
||||||
|
QueryBarInput,
|
||||||
|
fromUser,
|
||||||
|
toUser,
|
||||||
|
setupDirective as setupQueryBarDirective,
|
||||||
|
} from './query_bar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query Service
|
* Query Service
|
||||||
|
@ -35,6 +41,7 @@ export class QueryService {
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
QueryBar,
|
QueryBar,
|
||||||
|
QueryBarInput,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue