From 89c103a8962d95b532ce3a7c71f0274b5b893e85 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 23 Feb 2021 10:31:48 +0100 Subject: [PATCH 01/50] [APM] Guard searches with range/additional queries (#92112) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/queries.test.ts.snap | 9 +++++ .../__fixtures__/versions_first_seen.json | 16 ++++----- .../get_derived_service_annotations.ts | 18 +++++----- .../lib/services/annotations/index.test.ts | 4 +-- .../get_services/get_legacy_data_status.ts | 12 ++++--- .../__snapshots__/queries.test.ts.snap | 34 +++++++++++++++++++ .../lib/transactions/breakdown/index.ts | 9 +++++ 7 files changed, 78 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 3e68831ee7cba7..0521ff7d9554d6 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -34,6 +34,15 @@ Object { }, }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/__fixtures__/versions_first_seen.json b/x-pack/plugins/apm/server/lib/services/annotations/__fixtures__/versions_first_seen.json index c53b28c8bf5943..8a277797825a45 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/__fixtures__/versions_first_seen.json +++ b/x-pack/plugins/apm/server/lib/services/annotations/__fixtures__/versions_first_seen.json @@ -9,16 +9,16 @@ }, "hits": { "total": { - "value": 10000, + "value": 1, "relation": "gte" }, "max_score": null, - "hits": [] - }, - "aggregations": { - "first_seen": { - "value": 1.5281138E12, - "value_as_string": "2018-06-04T12:00:00.000Z" - } + "hits": [ + { + "_source": { + "@timestamp": "2018-06-04T12:00:00.000Z" + } + } + ] } } diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 67aa9d7fcd8707..25c42f403da2e5 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isNumber } from 'lodash'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; import { ESFilter } from '../../../../../../typings/elasticsearch'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { @@ -85,25 +85,23 @@ export async function getDerivedServiceAnnotations({ ], }, body: { - size: 0, + size: 1, query: { bool: { filter: [...filter, { term: { [SERVICE_VERSION]: version } }], }, }, - aggs: { - first_seen: { - min: { - field: '@timestamp', - }, - }, + sort: { + '@timestamp': 'desc', }, }, }); - const firstSeen = response.aggregations?.first_seen.value; + const firstSeen = new Date( + response.hits.hits[0]._source['@timestamp'] + ).getTime(); - if (!isNumber(firstSeen)) { + if (!isFiniteNumber(firstSeen)) { throw new Error( 'First seen for version was unexpectedly undefined or null.' ); diff --git a/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts b/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts index 206e04d49cc033..d86016ed9d505b 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/index.test.ts @@ -111,13 +111,13 @@ describe('getServiceAnnotations', () => { { id: '8.0.0', text: '8.0.0', - '@timestamp': 1.5281138e12, + '@timestamp': new Date('2018-06-04T12:00:00.000Z').getTime(), type: 'version', }, { id: '7.5.0', text: '7.5.0', - '@timestamp': 1.5281138e12, + '@timestamp': new Date('2018-06-04T12:00:00.000Z').getTime(), type: 'version', }, ]); diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts index 87f3c0a5d1b389..a3adca0d306aa7 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts @@ -5,15 +5,16 @@ * 2.0. */ +import { rangeQuery } from '../../../../common/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { OBSERVER_VERSION_MAJOR } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; // returns true if 6.x data is found -export async function getLegacyDataStatus(setup: Setup) { +export async function getLegacyDataStatus(setup: Setup & SetupTimeRange) { return withApmSpan('get_legacy_data_status', async () => { - const { apmEventClient } = setup; + const { apmEventClient, start, end } = setup; const params = { terminateAfter: 1, @@ -24,7 +25,10 @@ export async function getLegacyDataStatus(setup: Setup) { size: 0, query: { bool: { - filter: [{ range: { [OBSERVER_VERSION_MAJOR]: { lt: 7 } } }], + filter: [ + { range: { [OBSERVER_VERSION_MAJOR]: { lt: 7 } } }, + ...rangeQuery(start, end), + ], }, }, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap index 5d6a92a8741119..62050563497e94 100644 --- a/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap @@ -164,6 +164,23 @@ Object { "service.environment": "test", }, }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "span.self_time.sum.us", + }, + }, + Object { + "exists": Object { + "field": "transaction.breakdown.count", + }, + }, + ], + }, + }, ], }, }, @@ -298,6 +315,23 @@ Object { "service.environment": "test", }, }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "span.self_time.sum.us", + }, + }, + Object { + "exists": Object { + "field": "transaction.breakdown.count", + }, + }, + ], + }, + }, Object { "term": Object { "transaction.name": "baz", diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index c3741184c807db..f1e202df312c27 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -87,6 +87,15 @@ export function getTransactionBreakdown({ ...rangeQuery(start, end), ...environmentQuery(environment), ...esFilter, + { + bool: { + should: [ + { exists: { field: SPAN_SELF_TIME_SUM } }, + { exists: { field: TRANSACTION_BREAKDOWN_COUNT } }, + ], + minimum_should_match: 1, + }, + }, ]; if (transactionName) { From 2ce344019a8679c36fa2e28ffeec135a3304e279 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 23 Feb 2021 10:37:56 +0100 Subject: [PATCH 02/50] Add text search mode in global kuery bar (#91814) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...a-public.querystringinputprops.icontype.md | 2 +- ...ublic.querystringinputprops.isclearable.md | 11 +++ ...ugins-data-public.querystringinputprops.md | 5 +- ...public.querystringinputprops.nonkqlmode.md | 11 +++ ...uerystringinputprops.nonkqlmodehelptext.md | 11 +++ ...na-plugin-plugins-data-public.searchbar.md | 4 +- src/plugins/data/public/public.api.md | 13 ++- .../language_switcher.test.tsx | 93 ++++++++++++++++++- .../query_string_input/language_switcher.tsx | 47 ++++++++-- .../query_string_input/query_bar_top_row.tsx | 11 +++ .../query_string_input/query_string_input.tsx | 31 ++++++- .../ui/search_bar/create_search_bar.tsx | 6 ++ .../data/public/ui/search_bar/search_bar.tsx | 12 +++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 15 files changed, 236 insertions(+), 23 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.icontype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.icontype.md index 4b1c5b84557e7b..3de186cf775149 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.icontype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.icontype.md @@ -7,5 +7,5 @@ Signature: ```typescript -iconType?: string; +iconType?: EuiIconProps['type']; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md new file mode 100644 index 00000000000000..738041c2d57506 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [isClearable](./kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md) + +## QueryStringInputProps.isClearable property + +Signature: + +```typescript +isClearable?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md index 90c604d131800f..a3089cc5d4da94 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md @@ -19,10 +19,13 @@ export interface QueryStringInputProps | [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) | string | | | [disableAutoFocus](./kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md) | boolean | | | [disableLanguageSwitcher](./kibana-plugin-plugins-data-public.querystringinputprops.disablelanguageswitcher.md) | boolean | | -| [iconType](./kibana-plugin-plugins-data-public.querystringinputprops.icontype.md) | string | | +| [iconType](./kibana-plugin-plugins-data-public.querystringinputprops.icontype.md) | EuiIconProps['type'] | | | [indexPatterns](./kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md) | Array<IIndexPattern | string> | | +| [isClearable](./kibana-plugin-plugins-data-public.querystringinputprops.isclearable.md) | boolean | | | [isInvalid](./kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md) | boolean | | | [languageSwitcherPopoverAnchorPosition](./kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md) | PopoverAnchorPosition | | +| [nonKqlMode](./kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md) | 'lucene' | 'text' | | +| [nonKqlModeHelpText](./kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md) | string | | | [onBlur](./kibana-plugin-plugins-data-public.querystringinputprops.onblur.md) | () => void | | | [onChange](./kibana-plugin-plugins-data-public.querystringinputprops.onchange.md) | (query: Query) => void | | | [onChangeQueryInputFocus](./kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md) | (isFocused: boolean) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md new file mode 100644 index 00000000000000..809bf0bb56b289 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [nonKqlMode](./kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmode.md) + +## QueryStringInputProps.nonKqlMode property + +Signature: + +```typescript +nonKqlMode?: 'lucene' | 'text'; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md new file mode 100644 index 00000000000000..8caf492bebeb18 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [nonKqlModeHelpText](./kibana-plugin-plugins-data-public.querystringinputprops.nonkqlmodehelptext.md) + +## QueryStringInputProps.nonKqlModeHelpText property + +Signature: + +```typescript +nonKqlModeHelpText?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index 786ac4f9d61a90..193a2e5a24f3f4 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 664f4e16fa6d7d..63b766a82e8e62 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -35,6 +35,7 @@ import { EuiComboBoxProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiFlyoutSize } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; +import { EuiIconProps } from '@elastic/eui'; import { EventEmitter } from 'events'; import { ExecutionContext } from 'src/plugins/expressions/common'; import { ExpressionAstExpression } from 'src/plugins/expressions/common'; @@ -1998,14 +1999,20 @@ export interface QueryStringInputProps { // (undocumented) disableLanguageSwitcher?: boolean; // (undocumented) - iconType?: string; + iconType?: EuiIconProps['type']; // (undocumented) indexPatterns: Array; // (undocumented) + isClearable?: boolean; + // (undocumented) isInvalid?: boolean; // (undocumented) languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; // (undocumented) + nonKqlMode?: 'lucene' | 'text'; + // (undocumented) + nonKqlModeHelpText?: string; + // (undocumented) onBlur?: () => void; // (undocumented) onChange?: (query: Query) => void; @@ -2254,8 +2261,8 @@ export const SEARCH_SESSIONS_MANAGEMENT_ID = "search_sessions"; // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "placeholder" | "isLoading" | "iconType" | "indexPatterns" | "filters" | "dataTestSubj" | "isClearable" | "refreshInterval" | "nonKqlMode" | "nonKqlModeHelpText" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx index cc048b5ed9cacf..acbd48718d92ea 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx @@ -7,15 +7,15 @@ */ import React from 'react'; -import { QueryLanguageSwitcher } from './language_switcher'; +import { QueryLanguageSwitcher, QueryLanguageSwitcherProps } from './language_switcher'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { coreMock } from '../../../../../core/public/mocks'; import { mountWithIntl } from '@kbn/test/jest'; -import { EuiButtonEmpty, EuiPopover } from '@elastic/eui'; +import { EuiButtonEmpty, EuiIcon, EuiPopover } from '@elastic/eui'; const startMock = coreMock.createStart(); describe('LanguageSwitcher', () => { - function wrapInContext(testProps: any) { + function wrapInContext(testProps: QueryLanguageSwitcherProps) { const services = { uiSettings: startMock.uiSettings, docLinks: startMock.docLinks, @@ -55,4 +55,91 @@ describe('LanguageSwitcher', () => { expect(component.find(EuiPopover).prop('isOpen')).toBe(true); expect(component.find('[data-test-subj="languageToggle"]').get(0).props.checked).toBeTruthy(); }); + + it('should toggle off if language is text', () => { + const component = mountWithIntl( + wrapInContext({ + language: 'text', + onSelectLanguage: () => { + return; + }, + }) + ); + component.find(EuiButtonEmpty).simulate('click'); + expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + expect(component.find('[data-test-subj="languageToggle"]').get(0).props.checked).toBeFalsy(); + }); + it('it set language on nonKql mode text', () => { + const onSelectLanguage = jest.fn(); + + const component = mountWithIntl( + wrapInContext({ + language: 'kuery', + nonKqlMode: 'text', + onSelectLanguage, + }) + ); + component.find(EuiButtonEmpty).simulate('click'); + expect(component.find(EuiPopover).prop('isOpen')).toBe(true); + expect(component.find('[data-test-subj="languageToggle"]').get(0).props.checked).toBeTruthy(); + + component.find('[data-test-subj="languageToggle"]').at(1).simulate('click'); + + expect(onSelectLanguage).toHaveBeenCalledWith('text'); + }); + it('it set language on nonKql mode lucene', () => { + const onSelectLanguage = jest.fn(); + + const component = mountWithIntl( + wrapInContext({ + language: 'kuery', + nonKqlMode: 'lucene', + onSelectLanguage, + }) + ); + component.find(EuiButtonEmpty).simulate('click'); + component.find('[data-test-subj="languageToggle"]').at(1).simulate('click'); + + expect(onSelectLanguage).toHaveBeenCalledWith('lucene'); + }); + + it('it set language on kuery mode with nonKqlMode text', () => { + const onSelectLanguage = jest.fn(); + + const component = mountWithIntl( + wrapInContext({ + language: 'text', + nonKqlMode: 'text', + onSelectLanguage, + }) + ); + + expect(component.find(EuiIcon).prop('type')).toBe('boxesVertical'); + + component.find(EuiButtonEmpty).simulate('click'); + component.find('[data-test-subj="languageToggle"]').at(1).simulate('click'); + + expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); + }); + + it('it set language on kuery mode with nonKqlMode lucene', () => { + const onSelectLanguage = jest.fn(); + + const component = mountWithIntl( + wrapInContext({ + language: 'lucene', + nonKqlMode: 'lucene', + onSelectLanguage, + }) + ); + + expect(component.find('[data-test-subj="switchQueryLanguageButton"]').at(0).text()).toBe( + 'Lucene' + ); + + component.find(EuiButtonEmpty).simulate('click'); + component.find('[data-test-subj="languageToggle"]').at(1).simulate('click'); + + expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); + }); }); diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index c4afc2e05808a4..0c3659c079913c 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -10,6 +10,7 @@ import { EuiButtonEmpty, EuiForm, EuiFormRow, + EuiIcon, EuiLink, EuiPopover, EuiPopoverTitle, @@ -19,16 +20,25 @@ import { PopoverAnchorPosition, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { useKibana } from '../../../../kibana_react/public'; -interface Props { +export interface QueryLanguageSwitcherProps { language: string; onSelectLanguage: (newLanguage: string) => void; anchorPosition?: PopoverAnchorPosition; + nonKqlMode?: 'lucene' | 'text'; + nonKqlModeHelpText?: string; } -export function QueryLanguageSwitcher(props: Props) { +export function QueryLanguageSwitcher({ + language, + anchorPosition, + onSelectLanguage, + nonKqlMode = 'lucene', + nonKqlModeHelpText, +}: QueryLanguageSwitcherProps) { const kibana = useKibana(); const kueryQuerySyntaxDocs = kibana.services.docLinks!.links.query.kueryQuerySyntax; const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -38,6 +48,7 @@ export function QueryLanguageSwitcher(props: Props) { const kqlLabel = ( ); + const kqlFullName = ( ); + const kqlModeTitle = i18n.translate('data.query.queryBar.languageSwitcher.toText', { + defaultMessage: 'Switch to Kibana Query Language for search', + }); + const button = ( - {props.language === 'lucene' ? luceneLabel : kqlLabel} + {language === 'kuery' ? ( + kqlLabel + ) : nonKqlMode === 'lucene' ? ( + luceneLabel + ) : ( + + )} ); @@ -60,7 +81,7 @@ export function QueryLanguageSwitcher(props: Props) { setIsPopoverOpen(false)} @@ -79,13 +100,21 @@ export function QueryLanguageSwitcher(props: Props) { id="data.query.queryBar.syntaxOptionsDescription" defaultMessage="The {docsLink} (KQL) offers a simplified query syntax and support for scripted fields. KQL also provides autocomplete if you have - a Basic license or above. If you turn off KQL, Kibana uses Lucene." + a Basic license or above. If you turn off KQL, {nonKqlModeHelpText}" values={{ docsLink: ( {kqlFullName} ), + nonKqlModeHelpText: + nonKqlModeHelpText || + i18n.translate( + 'data.query.queryBar.syntaxOptionsDescription.nonKqlModeHelpText', + { + defaultMessage: 'Kibana uses Lucene.', + } + ), }} />

@@ -99,16 +128,16 @@ export function QueryLanguageSwitcher(props: Props) { id="queryEnhancementOptIn" name="popswitch" label={ - props.language === 'kuery' ? ( + language === 'kuery' ? ( ) : ( ) } - checked={props.language === 'kuery'} + checked={language === 'kuery'} onChange={() => { - const newLanguage = props.language === 'lucene' ? 'kuery' : 'lucene'; - props.onSelectLanguage(newLanguage); + const newLanguage = language === 'kuery' ? nonKqlMode : 'kuery'; + onSelectLanguage(newLanguage); }} data-test-subj="languageToggle" /> diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 61e894410437ec..4142bccc09f406 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -19,6 +19,7 @@ import { EuiSuperDatePicker, EuiFieldText, prettyDuration, + EuiIconProps, } from '@elastic/eui'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; @@ -57,6 +58,11 @@ export interface QueryBarTopRowProps { isDirty: boolean; timeHistory?: TimeHistoryContract; indicateNoData?: boolean; + iconType?: EuiIconProps['type']; + placeholder?: string; + isClearable?: boolean; + nonKqlMode?: 'lucene' | 'text'; + nonKqlModeHelpText?: string; } // Needed for React.lazy @@ -185,6 +191,11 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { onSubmit={onInputSubmit} persistedLog={persistedLog} dataTestSubj={props.dataTestSubj} + placeholder={props.placeholder} + isClearable={props.isClearable} + iconType={props.iconType} + nonKqlMode={props.nonKqlMode} + nonKqlModeHelpText={props.nonKqlModeHelpText} /> ); diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index aa2fc9e6314361..42ebfed42eb253 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -21,6 +21,7 @@ import { htmlIdGenerator, EuiPortal, EuiIcon, + EuiIconProps, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -56,7 +57,15 @@ export interface QueryStringInputProps { size?: SuggestionsListSize; className?: string; isInvalid?: boolean; - iconType?: string; + isClearable?: boolean; + iconType?: EuiIconProps['type']; + + /** + * @param nonKqlMode by default if language switch is enabled, user can switch between kql and lucene syntax mode + * this params add another option text, which is just a simple keyword search mode, the way a simple search box works + */ + nonKqlMode?: 'lucene' | 'text'; + nonKqlModeHelpText?: string; } interface Props extends QueryStringInputProps { @@ -698,10 +707,26 @@ export default class QueryStringInputUI extends Component {