Skip to content

Commit

Permalink
Update QueryBarInput to accept index pattern strings (elastic#36916)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Bargs authored May 24, 2019
1 parent ec1dc71 commit b7b7aa5
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
*/

export { QueryBar } from './query_bar';
export { QueryBarInput } from './query_bar_input';
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface Props {
disableAutoFocus?: boolean;
appName: string;
screenTitle: string;
indexPatterns: IndexPattern[];
indexPatterns: Array<IndexPattern | string>;
store: Storage;
intl: InjectedIntl;
prepend?: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@
import { createKfetch } from 'ui/kfetch/kfetch';
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(() => {
return {
getBasePath: () => `foo`,
Expand Down Expand Up @@ -52,6 +67,10 @@ const mockAutocompleteProvider = jest.fn(() => mockGetAutocompleteSuggestions);
export const mockGetAutocompleteProvider = jest.fn(() => mockAutocompleteProvider);
const mockKfetch = jest.fn(() => createKfetch(setup().http));

export const mockFetchIndexPatterns = jest
.fn()
.mockReturnValue(Promise.resolve([mockIndexPattern]));

jest.mock('ui/chrome', () => mockChromeFactory());
jest.mock('ui/kfetch', () => ({
kfetch: () => {},
Expand All @@ -72,6 +91,10 @@ jest.mock('ui/kfetch', () => ({
kfetch: mockKfetch,
}));

jest.mock('../lib/fetch_index_patterns', () => ({
fetchIndexPatterns: mockFetchIndexPatterns,
}));

import _ from 'lodash';
// Using doMock to avoid hoisting so that I can override only the debounce method in lodash
jest.doMock('lodash', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import {
mockFetchIndexPatterns,
mockGetAutocompleteProvider,
mockGetAutocompleteSuggestions,
mockPersistedLog,
Expand Down Expand Up @@ -131,6 +132,8 @@ describe('QueryBarInput', () => {
});

it('Should create a unique PersistedLog based on the appName and query language', () => {
mockPersistedLogFactory.mockClear();

mountWithIntl(
<QueryBarInput.WrappedComponent
query={kqlQuery}
Expand Down Expand Up @@ -240,4 +243,23 @@ describe('QueryBarInput', () => {
expect(mockGetAutocompleteProvider).toHaveBeenCalledWith('kuery');
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-*']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import {
AutocompleteSuggestionType,
getAutocompleteProvider,
} from 'ui/autocomplete_providers';
import { debounce, compact } from 'lodash';
import { IndexPattern } from 'ui/index_patterns';
import { debounce, compact, isEqual } from 'lodash';
import { IndexPattern, StaticIndexPattern } from 'ui/index_patterns';
import { PersistedLog } from 'ui/persisted_log';
import chrome from 'ui/chrome';
import { kfetch } from 'ui/kfetch';
Expand All @@ -38,14 +38,15 @@ import { fromUser, matchPairs, toUser } from '../lib';
import { QueryLanguageSwitcher } from './language_switcher';
import { SuggestionsComponent } from './typeahead/suggestions_component';
import { getQueryLog } from '../lib/get_query_log';
import { fetchIndexPatterns } from '../lib/fetch_index_patterns';

interface Query {
query: string;
language: string;
}

interface Props {
indexPatterns: IndexPattern[];
indexPatterns: Array<IndexPattern | string>;
intl: InjectedIntl;
query: Query;
appName: string;
Expand All @@ -65,6 +66,7 @@ interface State {
suggestionLimit: number;
selectionStart: number | null;
selectionEnd: number | null;
indexPatterns: StaticIndexPattern[];
}

const KEY_CODES = {
Expand All @@ -90,6 +92,7 @@ export class QueryBarInputUI extends Component<Props, State> {
suggestionLimit: 50,
selectionStart: null,
selectionEnd: null,
indexPatterns: [],
};

public inputRef: HTMLInputElement | null = null;
Expand All @@ -101,6 +104,21 @@ export class QueryBarInputUI extends Component<Props, State> {
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 () => {
if (!this.inputRef) {
return;
Expand All @@ -114,13 +132,13 @@ export class QueryBarInputUI extends Component<Props, State> {
const autocompleteProvider = getAutocompleteProvider(language);
if (
!autocompleteProvider ||
!Array.isArray(this.props.indexPatterns) ||
compact(this.props.indexPatterns).length === 0
!Array.isArray(this.state.indexPatterns) ||
compact(this.state.indexPatterns).length === 0
) {
return recentSearchSuggestions;
}

const indexPatterns = this.props.indexPatterns;
const indexPatterns = this.state.indexPatterns;
const getAutocompleteSuggestions = autocompleteProvider({ config, indexPatterns });

const { selectionStart, selectionEnd } = this.inputRef;
Expand Down Expand Up @@ -368,14 +386,20 @@ export class QueryBarInputUI extends Component<Props, State> {
this.persistedLog = this.props.persistedLog
? this.props.persistedLog
: getQueryLog(this.props.appName, this.props.query.language);
this.updateSuggestions();

this.fetchIndexPatterns().then(this.updateSuggestions);
}

public componentDidUpdate(prevProps: Props) {
this.persistedLog = this.props.persistedLog
? this.props.persistedLog
: getQueryLog(this.props.appName, this.props.query.language);
this.updateSuggestions();

if (!isEqual(prevProps.indexPatterns, this.props.indexPatterns)) {
this.fetchIndexPatterns().then(this.updateSuggestions);
} else {
this.updateSuggestions();
}

if (this.state.selectionStart !== null && this.state.selectionEnd !== null) {
if (this.inputRef) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

export { QueryBar } from './components';
export { QueryBar, QueryBarInput } from './components';
export { fromUser } from './lib/from_user';
export { toUser } from './lib/to_user';

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
};
9 changes: 8 additions & 1 deletion src/legacy/core_plugins/data/public/query/query_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
*/

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
Expand All @@ -35,6 +41,7 @@ export class QueryService {
},
ui: {
QueryBar,
QueryBarInput,
},
};
}
Expand Down

0 comments on commit b7b7aa5

Please sign in to comment.