diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index 111b0a2113403..9ea517d45eea1 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -116,10 +116,10 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, > {Array.isArray(topValues) ? topValues.map((value) => { - const fieldValue = - value.key_as_string ?? (value.key ? value.key.toString() : EMPTY_EXAMPLE); + const fieldValue = value.key_as_string ?? (value.key ? value.key.toString() : ''); + const displayValue = fieldValue ?? EMPTY_EXAMPLE; return ( - + = ({ stats, fieldFormat, barColor, compressed, /> {fieldName !== undefined && - fieldValue !== undefined && + displayValue !== undefined && onAddFilter !== undefined ? (
= ({ stats, fieldFormat, barColor, compressed, 'xpack.dataVisualizer.dataGrid.field.addFilterAriaLabel', { defaultMessage: 'Filter for {fieldName}: "{value}"', - values: { fieldName, value: fieldValue }, + values: { fieldName, value: displayValue }, } )} - data-test-subj={`dvFieldDataTopValuesAddFilterButton-${fieldName}-${fieldValue}`} + data-test-subj={`dvFieldDataTopValuesAddFilterButton-${fieldName}-${displayValue}`} style={{ minHeight: 'auto', minWidth: 'auto', @@ -172,10 +172,10 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, 'xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel', { defaultMessage: 'Filter out {fieldName}: "{value}"', - values: { fieldName, value: fieldValue }, + values: { fieldName, value: displayValue }, } )} - data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${fieldName}-${fieldValue}`} + data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${fieldName}-${displayValue}`} style={{ minHeight: 'auto', minWidth: 'auto', diff --git a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_data.ts b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_data.ts index 65c882ba551d9..9b85720fc1df4 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_data.ts @@ -14,9 +14,9 @@ import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; import { merge } from 'rxjs'; import { RandomSampler } from '@kbn/ml-random-sampler-utils'; import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; -import { Query } from '@kbn/es-query'; +import { buildEsQuery, Query } from '@kbn/es-query'; import { SearchQueryLanguage } from '@kbn/ml-query-utils'; -import { createMergedEsQuery } from '../../index_data_visualizer/utils/saved_search_utils'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import { useDataDriftStateManagerContext } from '../../data_drift/use_state_manager'; import type { InitialSettings } from '../../data_drift/use_data_drift_result'; import { @@ -74,7 +74,7 @@ export const useData = ( () => { const searchQuery = searchString !== undefined && searchQueryLanguage !== undefined - ? { query: searchString, language: searchQueryLanguage } + ? ({ query: searchString, language: searchQueryLanguage } as Query) : undefined; const timefilterActiveBounds = timeRange ?? timefilter.getActiveBounds(); @@ -90,24 +90,24 @@ export const useData = ( runtimeFieldMap: selectedDataView.getRuntimeMappings(), }; - const refQuery = createMergedEsQuery( - searchQuery, + const refQuery = buildEsQuery( + selectedDataView, + searchQuery ?? [], mapAndFlattenFilters([ ...queryManager.filterManager.getFilters(), ...(referenceStateManager.filters ?? []), ]), - selectedDataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); - const compQuery = createMergedEsQuery( - searchQuery, + const compQuery = buildEsQuery( + selectedDataView, + searchQuery ?? [], mapAndFlattenFilters([ ...queryManager.filterManager.getFilters(), ...(comparisonStateManager.filters ?? []), ]), - selectedDataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); return { diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts b/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts index 07b74677e8ea9..05f24bdcb7b68 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/use_data_drift_result.ts @@ -8,6 +8,7 @@ import { chunk, cloneDeep, flatten } from 'lodash'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { lastValueFrom } from 'rxjs'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { @@ -30,7 +31,7 @@ import { computeChi2PValue, type Histogram } from '@kbn/ml-chi2test'; import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; import type { AggregationsMultiTermsBucketKeys } from '@elastic/elasticsearch/lib/api/types'; -import { createMergedEsQuery } from '../index_data_visualizer/utils/saved_search_utils'; +import { buildEsQuery } from '@kbn/es-query'; import { useDataVisualizerKibana } from '../kibana_context'; import { useDataDriftStateManagerContext } from './use_state_manager'; @@ -758,18 +759,18 @@ export const useFetchDataComparisonResult = ( const kqlQuery = searchString !== undefined && searchQueryLanguage !== undefined - ? { query: searchString, language: searchQueryLanguage } + ? ({ query: searchString, language: searchQueryLanguage } as Query) : undefined; const refDataQuery = getDataComparisonQuery({ - searchQuery: createMergedEsQuery( - kqlQuery, + searchQuery: buildEsQuery( + currentDataView, + kqlQuery ?? [], mapAndFlattenFilters([ ...queryManager.filterManager.getFilters(), ...(referenceStateManager.filters ?? []), ]), - currentDataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ), datetimeField: currentDataView?.timeFieldName, runtimeFields, @@ -827,14 +828,14 @@ export const useFetchDataComparisonResult = ( setLoaded(0.25); const prodDataQuery = getDataComparisonQuery({ - searchQuery: createMergedEsQuery( - kqlQuery, + searchQuery: buildEsQuery( + currentDataView, + kqlQuery ?? [], mapAndFlattenFilters([ ...queryManager.filterManager.getFilters(), ...(comparisonStateManager.filters ?? []), ]), - currentDataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ), datetimeField: currentDataView?.timeFieldName, runtimeFields, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index cc17387886071..5d8ebe9e44d57 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -8,6 +8,7 @@ import { css } from '@emotion/react'; import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react'; import type { Required } from 'utility-types'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import { useEuiBreakpoint, @@ -21,7 +22,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { type Filter, FilterStateStore, type Query } from '@kbn/es-query'; +import { type Filter, FilterStateStore, type Query, buildEsQuery } from '@kbn/es-query'; import { generateFilters } from '@kbn/data-plugin/public'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { usePageUrlState, useUrlState } from '@kbn/ml-url-state'; @@ -62,7 +63,6 @@ import { DocumentCountContent } from '../../../common/components/document_count_ import { OMIT_FIELDS } from '../../../../../common/constants'; import { SearchPanel } from '../search_panel'; import { ActionsPanel } from '../actions_panel'; -import { createMergedEsQuery } from '../../utils/saved_search_utils'; import { DataVisualizerDataViewManagement } from '../data_view_management'; import type { GetAdditionalLinks } from '../../../common/components/results_links'; import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; @@ -389,14 +389,14 @@ export const IndexDataVisualizerView: FC = (dataVi language: searchQueryLanguage, }; - const combinedQuery = createMergedEsQuery( + const combinedQuery = buildEsQuery( + currentDataView, { query: searchString || '', language: searchQueryLanguage, }, data.query.filterManager.getFilters() ?? [], - currentDataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); setSearchParams({ diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx index 3ad691bbe11ce..d0f6812c4e253 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { isDefined } from '@kbn/ml-is-defined'; import { DataView } from '@kbn/data-views-plugin/common'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; -import { createMergedEsQuery } from '../../utils/saved_search_utils'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import { useDataVisualizerKibana } from '../../../kibana_context'; export const SearchPanelContent = ({ @@ -63,16 +63,17 @@ export const SearchPanelContent = ({ const searchHandler = ({ query, filters }: { query?: Query; filters?: Filter[] }) => { const mergedQuery = isDefined(query) ? query : searchInput; const mergedFilters = isDefined(filters) ? filters : queryManager.filterManager.getFilters(); + try { if (mergedFilters) { queryManager.filterManager.setFilters(mergedFilters); } - const combinedQuery = createMergedEsQuery( - mergedQuery, - queryManager.filterManager.getFilters() ?? [], + const combinedQuery = buildEsQuery( dataView, - uiSettings + mergedQuery ? [mergedQuery] : [], + queryManager.filterManager.getFilters() ?? [], + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); setSearchParams({ diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts index b012d049ae04f..4570a2019af26 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts @@ -137,10 +137,11 @@ export const useDataVisualizerGridData = ( }); if (searchData === undefined || dataVisualizerListState.searchString !== '') { - if (dataVisualizerListState.filters) { + if (filterManager) { const globalFilters = filterManager?.getGlobalFilters(); - if (filterManager) filterManager.setFilters(dataVisualizerListState.filters); + if (dataVisualizerListState.filters) + filterManager.setFilters(dataVisualizerListState.filters); if (globalFilters) filterManager?.addFilters(globalFilters); } return { @@ -169,6 +170,7 @@ export const useDataVisualizerGridData = ( currentFilters, }), lastRefresh, + data.query.filterManager, ]); const _timeBuckets = useTimeBuckets(); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts index c43483a34e34c..2b25a5e8d2b8c 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.test.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { - getQueryFromSavedSearchObject, - createMergedEsQuery, - getEsQueryFromSavedSearch, -} from './saved_search_utils'; +import { getQueryFromSavedSearchObject, getEsQueryFromSavedSearch } from './saved_search_utils'; import type { SavedSearchSavedObject } from '../../../../common/types'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { type Filter, FilterStateStore } from '@kbn/es-query'; +import { FilterStateStore } from '@kbn/es-query'; import { stubbedSavedObjectIndexPattern } from '@kbn/data-views-plugin/common/data_view.stub'; import { DataView } from '@kbn/data-views-plugin/public'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; @@ -217,80 +213,6 @@ describe('getQueryFromSavedSearchObject()', () => { }); }); -describe('createMergedEsQuery()', () => { - const luceneQuery = { - query: 'responsetime:>50', - language: 'lucene', - }; - const kqlQuery = { - query: 'responsetime > 49', - language: 'kuery', - }; - const mockFilters: Filter[] = [ - { - meta: { - index: '90a978e0-1c80-11ec-b1d7-f7e5cf21b9e0', - negate: false, - disabled: false, - alias: null, - type: 'phrase', - key: 'airline', - params: { - query: 'ASA', - }, - }, - query: { - match: { - airline: { - query: 'ASA', - type: 'phrase', - }, - }, - }, - $state: { - store: 'appState' as FilterStateStore, - }, - }, - ]; - - it('return formatted ES bool query with both the original query and filters combined', () => { - expect(createMergedEsQuery(luceneQuery, mockFilters)).toEqual({ - bool: { - filter: [{ match_phrase: { airline: { query: 'ASA' } } }], - must: [{ query_string: { query: 'responsetime:>50' } }], - must_not: [], - should: [], - }, - }); - expect(createMergedEsQuery(kqlQuery, mockFilters)).toEqual({ - bool: { - filter: [{ match_phrase: { airline: { query: 'ASA' } } }], - minimum_should_match: 1, - must_not: [], - should: [{ range: { responsetime: { gt: '49' } } }], - }, - }); - }); - it('return formatted ES bool query without filters ', () => { - expect(createMergedEsQuery(luceneQuery)).toEqual({ - bool: { - filter: [], - must: [{ query_string: { query: 'responsetime:>50' } }], - must_not: [], - should: [], - }, - }); - expect(createMergedEsQuery(kqlQuery)).toEqual({ - bool: { - filter: [], - minimum_should_match: 1, - must_not: [], - should: [{ range: { responsetime: { gt: '49' } } }], - }, - }); - }); -}); - describe('getEsQueryFromSavedSearch()', () => { it('return undefined if saved search is not provided', () => { expect( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.ts index 04bc52bf08057..3ecc8a3a7a3d8 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/utils/saved_search_utils.ts @@ -9,22 +9,15 @@ // `x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx` import { cloneDeep } from 'lodash'; import { IUiSettingsClient } from '@kbn/core/public'; -import { - fromKueryExpression, - toElasticsearchQuery, - buildQueryFromFilters, - buildEsQuery, - Query, - Filter, - AggregateQuery, -} from '@kbn/es-query'; +import { buildEsQuery, Query, Filter } from '@kbn/es-query'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView } from '@kbn/data-views-plugin/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { getEsQueryConfig, isQuery, SearchSource } from '@kbn/data-plugin/common'; +import { getEsQueryConfig, SearchSource } from '@kbn/data-plugin/common'; import { FilterManager, mapAndFlattenFilters } from '@kbn/data-plugin/public'; import { getDefaultDSLQuery } from '@kbn/ml-query-utils'; -import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '@kbn/ml-query-utils'; +import { SearchQueryLanguage } from '@kbn/ml-query-utils'; +import { isDefined } from '@kbn/ml-is-defined'; import { isSavedSearchSavedObject, SavedSearchSavedObject } from '../../../../common/types'; /** @@ -59,53 +52,8 @@ export function getQueryFromSavedSearchObject(savedSearch: SavedSearchSavedObjec return parsed; } -/** - * Create an Elasticsearch query that combines both lucene/kql query string and filters - * Should also form a valid query if only the query or filters is provided - */ -export function createMergedEsQuery( - query?: Query | AggregateQuery | undefined, - filters?: Filter[], - dataView?: DataView, - uiSettings?: IUiSettingsClient -) { - let combinedQuery = getDefaultDSLQuery() as QueryDslQueryContainer; - - if (isQuery(query) && query.language === SEARCH_QUERY_LANGUAGE.KUERY) { - const ast = fromKueryExpression(query.query); - if (query.query !== '') { - combinedQuery = toElasticsearchQuery(ast, dataView); - } - if (combinedQuery.bool !== undefined) { - const filterQuery = buildQueryFromFilters(filters, dataView); - - if (!Array.isArray(combinedQuery.bool.filter)) { - combinedQuery.bool.filter = - combinedQuery.bool.filter === undefined ? [] : [combinedQuery.bool.filter]; - } - - if (!Array.isArray(combinedQuery.bool.must_not)) { - combinedQuery.bool.must_not = - combinedQuery.bool.must_not === undefined ? [] : [combinedQuery.bool.must_not]; - } - - combinedQuery.bool.filter = [...combinedQuery.bool.filter, ...filterQuery.filter]; - combinedQuery.bool.must_not = [...combinedQuery.bool.must_not, ...filterQuery.must_not]; - } - } else { - combinedQuery = buildEsQuery( - dataView, - query ? [query] : [], - filters ? filters : [], - uiSettings ? getEsQueryConfig(uiSettings) : undefined - ); - } - - return combinedQuery; -} - -function getSavedSearchSource(savedSearch: SavedSearch) { - return savedSearch && +function getSavedSearchSource(savedSearch?: SavedSearch | null) { + return isDefined(savedSearch) && 'searchSource' in savedSearch && savedSearch?.searchSource instanceof SearchSource ? savedSearch.searchSource @@ -131,11 +79,15 @@ export function getEsQueryFromSavedSearch({ filters?: Filter[]; filterManager?: FilterManager; }) { - if (!dataView || !savedSearch) return; + if (!dataView && !savedSearch) return; const userQuery = query; const userFilters = filters; + if (filterManager && userFilters) { + filterManager.addFilters(userFilters); + } + const savedSearchSource = getSavedSearchSource(savedSearch); // If saved search has a search source with nested parent @@ -146,8 +98,8 @@ export function getEsQueryFromSavedSearch({ // Flattened query from search source may contain a clause that narrows the time range // which might interfere with global time pickers so we need to remove const savedQuery = - cloneDeep(savedSearch.searchSource.getSearchRequestBody()?.query) ?? getDefaultDSLQuery(); - const timeField = savedSearch.searchSource.getField('index')?.timeFieldName; + cloneDeep(savedSearchSource.getSearchRequestBody()?.query) ?? getDefaultDSLQuery(); + const timeField = savedSearchSource.getField('index')?.timeFieldName; if (Array.isArray(savedQuery.bool.filter) && timeField !== undefined) { savedQuery.bool.filter = savedQuery.bool.filter.filter( @@ -155,6 +107,7 @@ export function getEsQueryFromSavedSearch({ !(c.hasOwnProperty('range') && c.range?.hasOwnProperty(timeField)) ); } + return { searchQuery: savedQuery, searchString: userQuery.query, @@ -163,39 +116,38 @@ export function getEsQueryFromSavedSearch({ } // If no saved search available, use user's query and filters - if (!savedSearch && userQuery) { - if (filterManager && userFilters) filterManager.addFilters(userFilters); - - const combinedQuery = createMergedEsQuery( - userQuery, - Array.isArray(userFilters) ? userFilters : [], + if ( + !savedSearch && + (userQuery || userFilters || (filterManager && filterManager.getGlobalFilters()?.length > 0)) + ) { + const combinedQuery = buildEsQuery( dataView, - uiSettings + userQuery ?? [], + filterManager?.getFilters() ?? [], + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); return { searchQuery: combinedQuery, - searchString: userQuery.query, - queryLanguage: userQuery.language as SearchQueryLanguage, + searchString: userQuery?.query ?? '', + queryLanguage: (userQuery?.language ?? 'kuery') as SearchQueryLanguage, }; } // If saved search available, merge saved search with the latest user query or filters // which might differ from extracted saved search data if (savedSearchSource) { - const globalFilters = filterManager?.getGlobalFilters(); // FIXME: Add support for AggregateQuery type #150091 const currentQuery = userQuery ?? (savedSearchSource.getField('query') as Query); const currentFilters = userFilters ?? mapAndFlattenFilters(savedSearchSource.getField('filter') as Filter[]); - if (filterManager) filterManager.setFilters(currentFilters); - if (globalFilters) filterManager?.addFilters(globalFilters); + if (filterManager) filterManager.addFilters(currentFilters); - const combinedQuery = createMergedEsQuery( + const combinedQuery = buildEsQuery( + dataView, currentQuery, filterManager ? filterManager?.getFilters() : currentFilters, - dataView, - uiSettings + uiSettings ? getEsQueryConfig(uiSettings) : undefined ); return {