[7.x] Enable use of KQL and autocomplete in filters agg editor (#37287) (#38468)

* Enable use of KQL and autocomplete in filters agg editor (#37287)

This PR updates the filters agg editor to use the full QueryBar component, enabling use of KQL and autocomplete inside the editor for this aggregation in Visualize.

* remove unused translation
This commit is contained in:
Matt Bargar 2019-06-10 18:24:20 -05:00 committed by GitHub
parent 2fd33c9371
commit e1a19ccd57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 714 additions and 549 deletions

View file

@ -18,11 +18,17 @@
*/
// Creates a filter corresponding to a raw Elasticsearch query DSL object
export function buildQueryFilter(query, index) {
return {
export function buildQueryFilter(query, index, alias) {
const filter = {
query: query,
meta: {
index: index
index,
}
};
if (alias) {
filter.meta.alias = alias;
}
return filter;
}

View file

@ -92,3 +92,4 @@ export { ExpressionRenderer, ExpressionRendererProps, ExpressionRunner } from '.
/** @public types */
export { IndexPattern, StaticIndexPattern, StaticIndexPatternField, Field } from './index_patterns';
export { Query } from './query';

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { QueryService, QuerySetup } from './query_service';
export { QueryService, QuerySetup, Query } from './query_service';

View file

@ -170,174 +170,170 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto
}
}
>
<form
name="queryBarForm"
<div
role="search"
>
<div
role="search"
className="kuiLocalSearchAssistedInput"
>
<div
className="kuiLocalSearchAssistedInput"
<EuiFieldText
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={false}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFieldText
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={false}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
compressed={false}
fullWidth={true}
isLoading={false}
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
>
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
className="euiFormControlLayout__childrenWrapper"
>
<div
className="euiFormControlLayout__childrenWrapper"
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={false}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={false}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="kuery"
onSelectLanguage={[Function]}
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="kuery"
onSelectLanguage={[Function]}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
>
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
className="euiPopover__anchor"
>
<div
className="euiPopover__anchor"
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<EuiButtonEmpty
color="primary"
iconSide="left"
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
size="xs"
type="button"
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
type="button"
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__content"
className="euiButtonEmpty__text"
>
<span
className="euiButtonEmpty__text"
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
>
KQL
</FormattedMessage>
</span>
KQL
</FormattedMessage>
</span>
</button>
</EuiButtonEmpty>
</div>
</span>
</button>
</EuiButtonEmpty>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</form>
</div>
<SuggestionsComponent
index={null}
loadMore={[Function]}
@ -520,174 +516,170 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1
}
}
>
<form
name="queryBarForm"
<div
role="search"
>
<div
role="search"
className="kuiLocalSearchAssistedInput"
>
<div
className="kuiLocalSearchAssistedInput"
<EuiFieldText
append={
<QueryLanguageSwitcher
language="lucene"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFieldText
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="lucene"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="lucene"
onSelectLanguage={[Function]}
/>
}
compressed={false}
fullWidth={true}
isLoading={false}
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
>
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
className="euiFormControlLayout__childrenWrapper"
>
<div
className="euiFormControlLayout__childrenWrapper"
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="lucene"
onSelectLanguage={[Function]}
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="lucene"
onSelectLanguage={[Function]}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="Lucene"
id="data.query.queryBar.luceneLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="Lucene"
id="data.query.queryBar.luceneLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
>
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
className="euiPopover__anchor"
>
<div
className="euiPopover__anchor"
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<EuiButtonEmpty
color="primary"
iconSide="left"
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
size="xs"
type="button"
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
type="button"
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__content"
className="euiButtonEmpty__text"
>
<span
className="euiButtonEmpty__text"
<FormattedMessage
defaultMessage="Lucene"
id="data.query.queryBar.luceneLanguageName"
values={Object {}}
>
<FormattedMessage
defaultMessage="Lucene"
id="data.query.queryBar.luceneLanguageName"
values={Object {}}
>
Lucene
</FormattedMessage>
</span>
Lucene
</FormattedMessage>
</span>
</button>
</EuiButtonEmpty>
</div>
</span>
</button>
</EuiButtonEmpty>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</form>
</div>
<SuggestionsComponent
index={null}
loadMore={[Function]}
@ -870,174 +862,170 @@ exports[`QueryBarInput Should render the given query 1`] = `
}
}
>
<form
name="queryBarForm"
<div
role="search"
>
<div
role="search"
className="kuiLocalSearchAssistedInput"
>
<div
className="kuiLocalSearchAssistedInput"
<EuiFieldText
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFieldText
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
compressed={false}
data-test-subj="queryInput"
fullWidth={true}
inputRef={[Function]}
isLoading={false}
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
>
<EuiFormControlLayout
append={
<QueryLanguageSwitcher
language="kuery"
onSelectLanguage={[Function]}
/>
}
compressed={false}
fullWidth={true}
isLoading={false}
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
>
<div
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--group"
className="euiFormControlLayout__childrenWrapper"
>
<div
className="euiFormControlLayout__childrenWrapper"
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<EuiValidatableControl
className="undefined euiFormControlLayout__child--noStyle"
>
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
<input
aria-activedescendant=""
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-label="You are on search box of Another Screen page. Start typing to search and filter the discover"
autoComplete="off"
autoFocus={true}
className="euiFieldText euiFieldText--fullWidth euiFieldText--inGroup"
data-test-subj="queryInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
placeholder="Search"
role="textbox"
spellCheck={false}
type="text"
value="response:200"
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="kuery"
onSelectLanguage={[Function]}
</EuiValidatableControl>
<EuiFormControlLayoutIcons
isLoading={false}
/>
</div>
<QueryLanguageSwitcher
className="euiFormControlLayout__append"
language="kuery"
onSelectLanguage={[Function]}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
/>
</EuiButtonEmpty>
}
className="eui-displayBlock"
closePopover={[Function]}
hasArrow={true}
id="popover"
isOpen={false}
ownFocus={true}
panelPaddingSize="m"
withTitle={true}
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
>
<EuiOutsideClickDetector
isDisabled={true}
onOutsideClick={[Function]}
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover euiPopover--anchorDownRight euiPopover--withTitle eui-displayBlock"
id="popover"
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
className="euiPopover__anchor"
>
<div
className="euiPopover__anchor"
<EuiButtonEmpty
color="primary"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
<EuiButtonEmpty
color="primary"
iconSide="left"
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
size="xs"
type="button"
>
<button
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
onClick={[Function]}
type="button"
<span
className="euiButtonEmpty__content"
>
<span
className="euiButtonEmpty__content"
className="euiButtonEmpty__text"
>
<span
className="euiButtonEmpty__text"
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
>
<FormattedMessage
defaultMessage="KQL"
id="data.query.queryBar.kqlLanguageName"
values={Object {}}
>
KQL
</FormattedMessage>
</span>
KQL
</FormattedMessage>
</span>
</button>
</EuiButtonEmpty>
</div>
</span>
</button>
</EuiButtonEmpty>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</div>
</EuiOutsideClickDetector>
</EuiPopover>
</QueryLanguageSwitcher>
</div>
</EuiFormControlLayout>
</EuiFieldText>
</div>
</form>
</div>
<SuggestionsComponent
index={null}
loadMore={[Function]}

View file

@ -27,6 +27,7 @@ import {
EuiSpacer,
EuiSwitch,
EuiText,
PopoverAnchorPosition,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
@ -41,6 +42,7 @@ interface State {
interface Props {
language: string;
onSelectLanguage: (newLanguage: string) => void;
anchorPosition?: PopoverAnchorPosition;
}
export class QueryLanguageSwitcher extends Component<Props, State> {
@ -73,7 +75,7 @@ export class QueryLanguageSwitcher extends Component<Props, State> {
id="popover"
className="eui-displayBlock"
ownFocus
anchorPosition="downRight"
anchorPosition={this.props.anchorPosition || 'downRight'}
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}

View file

@ -40,14 +40,10 @@ import { PersistedLog } from 'ui/persisted_log';
import { QueryBarInput } from './query_bar_input';
import { getQueryLog } from '../lib/get_query_log';
import { Query } from '../index';
const config = chrome.getUiSettingsClient();
interface Query {
query: string;
language: string;
}
interface DateRange {
from: string;
to: string;
@ -338,6 +334,7 @@ export class QueryBarUI extends Component<Props, State> {
const { query, language } = this.state.query;
if (
language === 'kuery' &&
typeof query === 'string' &&
!store.get('kibana.luceneSyntaxWarningOptOut') &&
doesKueryExpressionHaveLuceneSyntaxError(query)
) {

View file

@ -20,7 +20,7 @@
import { Component } from 'react';
import React from 'react';
import { EuiFieldText, EuiOutsideClickDetector } from '@elastic/eui';
import { EuiFieldText, EuiOutsideClickDetector, PopoverAnchorPosition } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import {
@ -28,34 +28,32 @@ import {
AutocompleteSuggestionType,
getAutocompleteProvider,
} from 'ui/autocomplete_providers';
import { debounce, compact, isEqual } from 'lodash';
import { debounce, compact, isEqual, omit } 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';
import { Storage } from 'ui/storage';
import { localStorage } from 'ui/storage/storage_service';
import { Query } from '../index';
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: Array<IndexPattern | string>;
intl: InjectedIntl;
query: Query;
appName: string;
id?: string;
disableAutoFocus?: boolean;
screenTitle?: string;
prepend?: any;
store: Storage;
store?: Storage;
persistedLog?: PersistedLog;
bubbleSubmitEvent?: boolean;
languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition;
onChange?: (query: Query) => void;
onSubmit?: (query: Query) => void;
}
@ -125,10 +123,10 @@ export class QueryBarInputUI extends Component<Props, State> {
return;
}
const {
query: { query, language },
} = this.props;
const recentSearchSuggestions = this.getRecentSearchSuggestions(query);
const language = this.props.query.language;
const queryString = this.getQueryString();
const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString);
const autocompleteProvider = getAutocompleteProvider(language);
if (
@ -148,7 +146,7 @@ export class QueryBarInputUI extends Component<Props, State> {
}
const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({
query,
query: queryString,
selectionStart,
selectionEnd,
});
@ -258,8 +256,11 @@ export class QueryBarInputUI extends Component<Props, State> {
}
break;
case KEY_CODES.ENTER:
event.preventDefault();
if (!this.props.bubbleSubmitEvent) {
event.preventDefault();
}
if (isSuggestionsVisible && index !== null && this.state.suggestions[index]) {
event.preventDefault();
this.selectSuggestion(this.state.suggestions[index]);
} else {
this.onSubmit(this.props.query);
@ -358,7 +359,11 @@ export class QueryBarInputUI extends Component<Props, State> {
body: JSON.stringify({ opt_in: language === 'kuery' }),
});
this.props.store.set('kibana.userQueryLanguage', language);
if (this.props.store) {
this.props.store.set('kibana.userQueryLanguage', language);
} else {
localStorage.set('kibana.userQueryLanguage', language);
}
const newQuery = { query: '', language };
this.onChange(newQuery);
@ -421,6 +426,22 @@ export class QueryBarInputUI extends Component<Props, State> {
}
public render() {
const rest = omit(this.props, [
'indexPatterns',
'intl',
'query',
'appName',
'disableAutoFocus',
'screenTitle',
'prepend',
'store',
'persistedLog',
'bubbleSubmitEvent',
'languageSwitcherPopoverAnchorPosition',
'onChange',
'onSubmit',
]);
return (
<EuiOutsideClickDetector onOutsideClick={this.onOutsideClick}>
<div
@ -431,63 +452,62 @@ export class QueryBarInputUI extends Component<Props, State> {
aria-owns="kbnTypeahead__items"
aria-controls="kbnTypeahead__items"
>
<form name="queryBarForm">
<div role="search">
<div className="kuiLocalSearchAssistedInput">
<EuiFieldText
id={this.props.id}
placeholder={this.props.intl.formatMessage({
id: 'data.query.queryBar.searchInputPlaceholder',
defaultMessage: 'Search',
})}
value={this.getQueryString()}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onChange={this.onInputChange}
onClick={this.onClickInput}
fullWidth
autoFocus={!this.props.disableAutoFocus}
inputRef={node => {
if (node) {
this.inputRef = node;
}
}}
autoComplete="off"
spellCheck={false}
aria-label={
this.props.screenTitle
? this.props.intl.formatMessage(
{
id: 'data.query.queryBar.searchInputAriaLabel',
defaultMessage:
'You are on search box of {previouslyTranslatedPageTitle} page. Start typing to search and filter the {pageType}',
},
{
previouslyTranslatedPageTitle: this.props.screenTitle,
pageType: this.props.appName,
}
)
: undefined
<div role="search">
<div className="kuiLocalSearchAssistedInput">
<EuiFieldText
placeholder={this.props.intl.formatMessage({
id: 'data.query.queryBar.searchInputPlaceholder',
defaultMessage: 'Search',
})}
value={this.getQueryString()}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onChange={this.onInputChange}
onClick={this.onClickInput}
fullWidth
autoFocus={!this.props.disableAutoFocus}
inputRef={node => {
if (node) {
this.inputRef = node;
}
type="text"
data-test-subj="queryInput"
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-activedescendant={
this.state.isSuggestionsVisible ? 'suggestion-' + this.state.index : ''
}
role="textbox"
prepend={this.props.prepend}
append={
<QueryLanguageSwitcher
language={this.props.query.language}
onSelectLanguage={this.onSelectLanguage}
/>
}
/>
</div>
}}
autoComplete="off"
spellCheck={false}
aria-label={
this.props.screenTitle
? this.props.intl.formatMessage(
{
id: 'data.query.queryBar.searchInputAriaLabel',
defaultMessage:
'You are on search box of {previouslyTranslatedPageTitle} page. Start typing to search and filter the {pageType}',
},
{
previouslyTranslatedPageTitle: this.props.screenTitle,
pageType: this.props.appName,
}
)
: undefined
}
type="text"
data-test-subj="queryInput"
aria-autocomplete="list"
aria-controls="kbnTypeahead__items"
aria-activedescendant={
this.state.isSuggestionsVisible ? 'suggestion-' + this.state.index : ''
}
role="textbox"
prepend={this.props.prepend}
append={
<QueryLanguageSwitcher
language={this.props.query.language}
anchorPosition={this.props.languageSwitcherPopoverAnchorPosition}
onSelectLanguage={this.onSelectLanguage}
/>
}
{...rest}
/>
</div>
</form>
</div>
<SuggestionsComponent
show={this.state.isSuggestionsVisible}

View file

@ -20,6 +20,12 @@
export { QueryBar, QueryBarInput } from './components';
export { fromUser } from './lib/from_user';
export { toUser } from './lib/to_user';
export { getQueryLog } from './lib/get_query_log';
// @ts-ignore
export { setupDirective } from './directive';
export interface Query {
query: string | { [key: string]: any };
language: string;
}

View file

@ -21,10 +21,10 @@ import angular from 'angular';
/**
* Take text from the model and present it to the user as a string
* @param {text} model value
* @param text model value
* @returns {string}
*/
export function toUser(text: ToUserQuery | string): string {
export function toUser(text: { [key: string]: any } | string): string {
if (text == null) {
return '';
}
@ -39,12 +39,3 @@ export function toUser(text: ToUserQuery | string): string {
}
return '' + text;
}
interface ToUserQuery {
match_all: object;
query_string: ToUserQueryString;
}
interface ToUserQueryString {
query: string;
}

View file

@ -23,6 +23,7 @@ import {
QueryBarInput,
fromUser,
toUser,
getQueryLog,
setupDirective as setupQueryBarDirective,
} from './query_bar';
@ -38,6 +39,7 @@ export class QueryService {
helpers: {
fromUser,
toUser,
getQueryLog,
},
ui: {
QueryBar,
@ -53,3 +55,5 @@ export class QueryService {
/** @public */
export type QuerySetup = ReturnType<QueryService['setup']>;
export { Query } from './query_bar';

View file

@ -27,14 +27,9 @@ import ResizeObserver from 'resize-observer-polyfill';
import { IndexPattern } from 'ui/index_patterns';
import { Storage } from 'ui/storage';
import { QueryBar } from '../../../query/query_bar';
import { Query, QueryBar } from '../../../query/query_bar';
import { FilterBar } from '../../../filter/filter_bar';
interface Query {
query: string;
language: string;
}
interface DateRange {
from: string;
to: string;
@ -45,10 +40,7 @@ interface DateRange {
* See [search_bar\directive\index.js] file
*/
interface Props {
query: {
query: string;
language: string;
};
query: Query;
onQuerySubmit: (payload: { dateRange: DateRange; query: Query }) => void;
disableAutoFocus?: boolean;
appName: string;

View file

@ -278,8 +278,40 @@ function transformFilterStringToQueryObject(doc) {
}
return newDoc;
}
function migrateFiltersAggQuery(doc) {
const visStateJSON = get(doc, 'attributes.visState');
if (visStateJSON) {
try {
const visState = JSON.parse(visStateJSON);
if (visState && visState.aggs) {
visState.aggs.forEach((agg) => {
if (agg.type !== 'filters') return;
agg.params.filters.forEach((filter) => {
if (filter.input.language) return filter;
filter.input.language = 'lucene';
});
});
return {
...doc,
attributes: {
...doc.attributes,
visState: JSON.stringify(visState),
},
};
}
} catch (e) {
// Let it go, the data is invalid and we'll leave it as is
}
}
return doc;
}
const executeMigrations720 = flow(migratePercentileRankAggregation, migrateDateHistogramAggregation);
const executeMigrations730 = flow(migrateGaugeVerticalSplitToAlignment, transformFilterStringToQueryObject);
const executeMigrations730 = flow(migrateGaugeVerticalSplitToAlignment, transformFilterStringToQueryObject, migrateFiltersAggQuery);
export const migrations = {
'index-pattern': {
@ -383,7 +415,7 @@ export const migrations = {
},
'7.0.1': removeDateHistogramTimeZones,
'7.2.0': doc => executeMigrations720(doc),
'7.3.0': executeMigrations730
'7.3.0': executeMigrations730,
},
dashboard: {
'7.0.0': (doc) => {

View file

@ -890,6 +890,90 @@ Object {
}
`);
});
describe('filters agg query migration', () => {
const doc = {
attributes: {
visState: JSON.stringify({
aggs: [
{
type: 'filters',
params: {
filters: [
{
input: {
query: 'response:200',
},
label: '',
},
{
input: {
query: 'response:404',
},
label: 'bad response',
},
{
input: {
query: {
exists: {
field: 'phpmemory',
},
},
},
label: '',
},
],
},
},
],
}),
},
};
it('should add language property to filters without one, assuming lucene', () => {
const migrationResult = migrate(doc);
expect(migrationResult).toEqual({
attributes: {
visState: JSON.stringify({
aggs: [
{
type: 'filters',
params: {
filters: [
{
input: {
query: 'response:200',
language: 'lucene',
},
label: '',
},
{
input: {
query: 'response:404',
language: 'lucene',
},
label: 'bad response',
},
{
input: {
query: {
exists: {
field: 'phpmemory',
},
},
language: 'lucene',
},
label: '',
},
],
},
},
],
}),
},
});
});
});
});
describe('7.3.0 tsvb', () => {
const migrate = doc => migrations.visualization['7.3.0'](doc);

View file

@ -43,8 +43,8 @@ describe('AggConfig Filters', function () {
schema: 'segment',
params: {
filters: [
{ input: { query: { query_string: { query: 'type:apache' } } } },
{ input: { query: { query_string: { query: 'type:nginx' } } } }
{ input: { query: 'type:apache', language: 'lucene' } },
{ input: { query: 'type:nginx', language: 'lucene' } }
]
}
}
@ -53,9 +53,9 @@ describe('AggConfig Filters', function () {
const aggConfig = vis.aggs.byTypeName.filters[0];
const filter = createFilterFilters(aggConfig, 'type:nginx');
expect(filter.query.query_string.query).to.be('type:nginx');
expect(filter.query.bool.must[0].query_string.query).to.be('type:nginx');
expect(filter.meta).to.have.property('index', indexPattern.id);
expect(filter.meta).to.have.property('alias', 'type:nginx');
});
});
});

View file

@ -26,6 +26,6 @@ export function createFilterFilters(aggConfig, key) {
const filter = dslFilters[key];
if (filter) {
return buildQueryFilter(filter.query, aggConfig.getIndexPattern().id);
return buildQueryFilter(filter.query, aggConfig.getIndexPattern().id, key);
}
}

View file

@ -22,11 +22,15 @@ import angular from 'angular';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterFilters } from './create_filter/filters';
import { decorateQuery, luceneStringToDsl } from '@kbn/es-query';
import { FiltersParamEditor } from '../controls/filters';
import { i18n } from '@kbn/i18n';
import chrome from 'ui/chrome';
import { buildEsQuery } from '@kbn/es-query';
import { data } from 'plugins/data';
const { getQueryLog } = data.query.helpers;
const config = chrome.getUiSettingsClient();
export const filtersBucketAgg = new BucketAggType({
name: 'filters',
@ -40,32 +44,36 @@ export const filtersBucketAgg = new BucketAggType({
{
name: 'filters',
editorComponent: FiltersParamEditor,
default: [ { input: {}, label: '' } ],
default: [ { input: { query: '', language: config.get('search:queryLanguage') }, label: '' } ],
write: function (aggConfig, output) {
const inFilters = aggConfig.params.filters;
if (!_.size(inFilters)) return;
const outFilters = _.transform(inFilters, function (filters, filter) {
const input = _.cloneDeep(filter.input);
inFilters.forEach((filter) => {
const persistedLog = getQueryLog('filtersAgg', filter.input.language);
persistedLog.add(filter.input.query);
});
if (!input) {
const outFilters = _.transform(inFilters, function (filters, filter) {
let input = _.cloneDeep(filter.input);
if (!input || !input.query) {
console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console
return;
}
const query = input.query = luceneStringToDsl(input.query);
const query = input = buildEsQuery(aggConfig.getIndexPattern(), [input], [], config);
if (!query) {
console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console
return;
}
const config = chrome.getUiSettingsClient();
const queryStringOptions = config.get('query:queryString:options');
decorateQuery(query, queryStringOptions);
const matchAllLabel = (filter.input.query === '' && _.has(query, 'match_all')) ? '*' : '';
const label = filter.label || matchAllLabel || _.get(query, 'query_string.query') || angular.toJson(query);
filters[label] = input;
const matchAllLabel = filter.input.query === '' ? '*' : '';
const label = filter.label
|| matchAllLabel
|| (typeof filter.input.query === 'string' ? filter.input.query : angular.toJson(filter.input.query));
filters[label] = { query: input };
}, {});
if (!_.size(outFilters)) return;

View file

@ -27,17 +27,22 @@ import {
EuiFormRow,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggConfig } from 'ui/vis';
import { Query, data } from 'plugins/data';
const { QueryBarInput } = data.query.ui;
interface FilterRowProps {
id: string;
arrayIndex: number;
customLabel: string;
value: string;
value: Query;
autoFocus: boolean;
disableRemove: boolean;
dataTestSubj: string;
onChangeValue(id: string, query: string, label: string): void;
onChangeValue(id: string, query: Query, label: string): void;
onRemoveFilter(id: string): void;
agg: AggConfig;
}
function FilterRow({
@ -48,6 +53,7 @@ function FilterRow({
autoFocus,
disableRemove,
dataTestSubj,
agg,
onChangeValue,
onRemoveFilter,
}: FilterRowProps) {
@ -94,15 +100,15 @@ function FilterRow({
fullWidth={true}
className="visEditorSidebar__aggParamFormRow"
>
<EuiFieldText
value={value}
placeholder={i18n.translate('common.ui.aggTypes.filters.filterPlaceholder', {
defaultMessage: 'Lucene or Query DSL',
})}
<QueryBarInput
query={value}
indexPatterns={[agg.getIndexPattern()]}
appName="filtersAgg"
onChange={query => onChangeValue(id, query, customLabel)}
disableAutoFocus={!autoFocus}
data-test-subj={dataTestSubj}
onChange={ev => onChangeValue(id, ev.target.value, customLabel)}
fullWidth={true}
autoFocus={autoFocus}
bubbleSubmitEvent={true}
languageSwitcherPopoverAnchorPosition="leftDown"
/>
</EuiFormRow>
{showCustomLabel ? (

View file

@ -22,14 +22,15 @@ import { omit, isEqual } from 'lodash';
import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui';
import { AggParamEditorProps } from 'ui/vis/editors/default';
import { FormattedMessage } from '@kbn/i18n/react';
import { data } from 'plugins/data';
import chrome from 'ui/chrome';
import { Query } from 'plugins/data';
import { FilterRow } from './filter';
const { toUser, fromUser } = data.query.helpers;
const generateId = htmlIdGenerator();
const config = chrome.getUiSettingsClient();
interface FilterValue {
input: any;
input: Query;
label: string;
id: string;
}
@ -41,11 +42,7 @@ function FiltersParamEditor({ agg, value, setValue }: AggParamEditorProps<Filter
useEffect(() => {
// set parsed values into model after initialization
setValue(
filters.map(filter =>
omit({ ...filter, input: { query: fromUser(filter.input.query) } }, 'id')
)
);
setValue(filters.map(filter => omit({ ...filter, input: filter.input }, 'id')));
}, []);
useEffect(
@ -68,15 +65,22 @@ function FiltersParamEditor({ agg, value, setValue }: AggParamEditorProps<Filter
};
const onAddFilter = () =>
updateFilters([...filters, { input: { query: '' }, label: '', id: generateId() }]);
updateFilters([
...filters,
{
input: { query: '', language: config.get('search:queryLanguage') },
label: '',
id: generateId(),
},
]);
const onRemoveFilter = (id: string) => updateFilters(filters.filter(filter => filter.id !== id));
const onChangeValue = (id: string, query: string, label: string) =>
const onChangeValue = (id: string, query: Query, label: string) =>
updateFilters(
filters.map(filter =>
filter.id === id
? {
...filter,
input: { query: fromUser(query) },
input: query,
label,
}
: filter
@ -91,10 +95,11 @@ function FiltersParamEditor({ agg, value, setValue }: AggParamEditorProps<Filter
id={id}
arrayIndex={arrayIndex}
customLabel={label}
value={toUser(input.query)}
value={input}
autoFocus={arrayIndex === filters.length - 1}
disableRemove={arrayIndex === 0 && filters.length === 1}
dataTestSubj={`visEditorFilterInput_${agg.id}_${arrayIndex}`}
agg={agg}
onChangeValue={onChangeValue}
onRemoveFilter={onRemoveFilter}
/>

View file

@ -0,0 +1,23 @@
/*
* 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 { Storage } from './storage';
export const localStorage = new Storage(window.localStorage);
export const sessionStorage = new Storage(window.sessionStorage);

View file

@ -18,6 +18,7 @@
*/
import { has } from 'lodash';
import { Query } from 'plugins/data';
/**
* Creates a standardized query object from old queries that were either strings or pure ES query DSL
@ -25,11 +26,12 @@ import { has } from 'lodash';
* @param query - a legacy query, what used to be stored in SearchSource's query property
* @return Object
*/
export function migrateLegacyQuery(query: object): object {
export function migrateLegacyQuery(query: Query | { [key: string]: any } | string): Query {
// Lucene was the only option before, so language-less queries are all lucene
if (!has(query, 'language')) {
return { query, language: 'lucene' };
}
return query;
return query as Query;
}

View file

@ -140,7 +140,6 @@
"common.ui.aggTypes.filters.addFilterButtonLabel": "フィルターを追加",
"common.ui.aggTypes.filters.definiteFilterLabel": "{index} ラベルでフィルタリング",
"common.ui.aggTypes.filters.filterLabel": "{index} でフィルタリング",
"common.ui.aggTypes.filters.filterPlaceholder": "Lucene またはクエリ DSL",
"common.ui.aggTypes.filters.labelPlaceholder": "ラベル",
"common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "このフィルターを削除",
"common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "フィルターラベルを切り替える",
@ -10001,4 +10000,4 @@
"xpack.watcher.watchActionsTitle": "条件が満たされた際に {watchActionsCount, plural, one{# アクション} other {# アクション}} を実行します",
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
}
}

View file

@ -139,7 +139,6 @@
"common.ui.aggTypes.filters.addFilterButtonLabel": "添加筛选",
"common.ui.aggTypes.filters.definiteFilterLabel": "筛选 {index} 标签",
"common.ui.aggTypes.filters.filterLabel": "筛选 {index}",
"common.ui.aggTypes.filters.filterPlaceholder": "Lucene 或查询 DSL",
"common.ui.aggTypes.filters.labelPlaceholder": "标签",
"common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "移除此筛选",
"common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "切换筛选标签",
@ -10011,4 +10010,4 @@
"xpack.watcher.watchActionsTitle": "满足后将执行 {watchActionsCount, plural, one{# 个操作} other {# 个操作}}",
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
}
}
}