From 8a7db5edd37f24fd05f67e868ec7b0047b5ebd5a Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 8 Nov 2024 12:04:47 +0100 Subject: [PATCH] [Discover] Remove field popover stats for ES|QL mode (#198948) - Related to https://github.com/elastic/kibana/pull/197538 ## Summary This PR removes the support of showing stats in the field popover in ES|QL mode as this UX will be revisited in the future to provide better results. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../field_stats/field_stats.test.tsx | 10 ++ .../components/field_stats/field_stats.tsx | 108 ++++------- .../field_list_item.tsx | 4 +- .../field_examples_calculator.test.ts | 34 ++-- .../field_examples_calculator.ts | 4 +- .../services/field_stats/field_stats_utils.ts | 2 +- .../field_stats_utils_text_based.test.ts | 129 -------------- .../field_stats_utils_text_based.ts | 156 ---------------- .../services/field_stats_text_based/index.ts | 10 -- .../load_field_stats_text_based.ts | 89 ---------- .../src/utils/can_provide_stats.test.ts | 99 +---------- .../src/utils/can_provide_stats.ts | 42 ++--- packages/kbn-unified-field-list/tsconfig.json | 1 - .../discover/group6/_sidebar_field_stats.ts | 167 +----------------- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 17 files changed, 82 insertions(+), 776 deletions(-) delete mode 100644 packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts delete mode 100644 packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts delete mode 100644 packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts delete mode 100644 packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts diff --git a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx index 951602334a622..beb0e1f05e1b8 100644 --- a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx +++ b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.test.tsx @@ -830,4 +830,14 @@ describe('UnifiedFieldList FieldStats', () => { expect(wrapper.text()).toBe('Summarymin29674max36821994Calculated from 5000 sample records.'); }); + + it('should not request field stats for ES|QL query', async () => { + const wrapper = await mountComponent( + + ); + + expect(loadFieldStats).toHaveBeenCalledTimes(0); + + expect(wrapper.text()).toBe('Analysis is not available for this field.'); + }); }); diff --git a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx index 8eada232cdeaf..58ff36069dd8c 100755 --- a/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx +++ b/packages/kbn-unified-field-list/src/components/field_stats/field_stats.tsx @@ -42,7 +42,6 @@ import { canProvideNumberSummaryForField, } from '../../utils/can_provide_stats'; import { loadFieldStats } from '../../services/field_stats'; -import { loadFieldStatsTextBased } from '../../services/field_stats_text_based'; import type { AddFieldFilterHandler } from '../../types'; import { FieldTopValues, @@ -136,7 +135,7 @@ const FieldStatsComponent: React.FC = ({ const [dataView, changeDataView] = useState(null); const abortControllerRef = useRef(null); const isCanceledRef = useRef(false); - const isTextBased = !!query && isOfAggregateQueryType(query); + const isEsqlQuery = !!query && isOfAggregateQueryType(query); const setState: typeof changeState = useCallback( (nextState) => { @@ -178,6 +177,12 @@ const FieldStatsComponent: React.FC = ({ setDataView(loadedDataView); + if (isEsqlQuery) { + // Not supported yet for ES|QL queries + // Previous implementation was removed in https://github.com/elastic/kibana/pull/198948/ + return; + } + if (state.isLoading) { return; } @@ -187,32 +192,17 @@ const FieldStatsComponent: React.FC = ({ abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); - const results = isTextBased - ? await loadFieldStatsTextBased({ - services: { data }, - dataView: loadedDataView, - field, - fromDate, - toDate, - baseQuery: query, - abortController: abortControllerRef.current, - }) - : await loadFieldStats({ - services: { data }, - dataView: loadedDataView, - field, - fromDate, - toDate, - dslQuery: - dslQuery ?? - buildEsQuery( - loadedDataView, - query ?? [], - filters ?? [], - getEsQueryConfig(uiSettings) - ), - abortController: abortControllerRef.current, - }); + const results = await loadFieldStats({ + services: { data }, + dataView: loadedDataView, + field, + fromDate, + toDate, + dslQuery: + dslQuery ?? + buildEsQuery(loadedDataView, query ?? [], filters ?? [], getEsQueryConfig(uiSettings)), + abortController: abortControllerRef.current, + }); abortControllerRef.current = null; @@ -297,7 +287,7 @@ const FieldStatsComponent: React.FC = ({ let title = <>; function combineWithTitleAndFooter(el: React.ReactElement) { - const countsElement = getCountsElement(state, services, isTextBased, dataTestSubject); + const countsElement = getCountsElement(state, services, isEsqlQuery, dataTestSubject); return ( <> @@ -319,7 +309,7 @@ const FieldStatsComponent: React.FC = ({ ); } - if (!canProvideStatsForField(field, isTextBased)) { + if (!canProvideStatsForField(field, isEsqlQuery)) { const messageNoAnalysis = ( = ({ : messageNoAnalysis; } - if (canProvideNumberSummaryForField(field, isTextBased) && isNumberSummaryValid(numberSummary)) { + if (canProvideNumberSummaryForField(field, isEsqlQuery) && isNumberSummaryValid(numberSummary)) { title = (
@@ -563,21 +553,19 @@ const FieldStatsComponent: React.FC = ({ function getCountsElement( state: FieldStatsState, services: FieldStatsServices, - isTextBased: boolean, + isEsqlQuery: boolean, dataTestSubject: string ): JSX.Element { const dataTestSubjDocsCount = 'unifiedFieldStats-statsFooter-docsCount'; const { fieldFormats } = services; - const { totalDocuments, sampledValues, sampledDocuments, topValues } = state; + const { totalDocuments, sampledDocuments } = state; - if (!totalDocuments) { + if (!totalDocuments || isEsqlQuery) { return <>; } - let labelElement; - - if (isTextBased) { - labelElement = topValues?.areExamples ? ( + const labelElement = + sampledDocuments && sampledDocuments < totalDocuments ? ( ) : ( {fieldFormats .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(sampledValues)} + .convert(totalDocuments)} ), }} /> ); - } else { - labelElement = - sampledDocuments && sampledDocuments < totalDocuments ? ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(sampledDocuments)} - - ), - }} - /> - ) : ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(totalDocuments)} - - ), - }} - /> - ); - } return ( diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx index 7864976c1180f..b139e7b5685c5 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx @@ -32,7 +32,7 @@ import type { UnifiedFieldListSidebarContainerStateService, AddFieldFilterHandler, } from '../../types'; -import { canProvideStatsForFieldTextBased } from '../../utils/can_provide_stats'; +import { canProvideStatsForEsqlField } from '../../utils/can_provide_stats'; interface GetCommonFieldItemButtonPropsParams { stateService: UnifiedFieldListSidebarContainerStateService; @@ -405,7 +405,7 @@ function UnifiedFieldListItemComponent({ /> )} renderContent={ - (searchMode === 'text-based' && canProvideStatsForFieldTextBased(field)) || + (searchMode === 'text-based' && canProvideStatsForEsqlField(field)) || searchMode === 'documents' ? renderPopover : undefined diff --git a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts index 6e14c16f7e42c..7e77bd7852726 100644 --- a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts +++ b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.test.ts @@ -223,7 +223,7 @@ describe('fieldExamplesCalculator', function () { values: getFieldValues(hits, dataView.fields.getByName('extension')!, dataView), field: dataView.fields.getByName('extension')!, count: 3, - isTextBased: false, + isEsqlQuery: false, }; }); @@ -286,33 +286,19 @@ describe('fieldExamplesCalculator', function () { expect(getFieldExampleBuckets(params).sampledValues).toBe(5); }); - it('works for text-based', function () { - const result = getFieldExampleBuckets({ - values: [['a'], ['b'], ['a'], ['a']], - field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, - isTextBased: true, - }); - expect(result).toMatchInlineSnapshot(` - Object { - "buckets": Array [ - Object { - "count": 3, - "key": "a", - }, - Object { - "count": 1, - "key": "b", - }, - ], - "sampledDocuments": 4, - "sampledValues": 4, - } - `); + it('should not work for ES|QL', function () { + expect(() => + getFieldExampleBuckets({ + values: [['a'], ['b'], ['a'], ['a']], + field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, + isEsqlQuery: true, + }) + ).toThrowError(); expect(() => getFieldExampleBuckets({ values: [['a'], ['b'], ['a'], ['a']], field: { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - isTextBased: true, + isEsqlQuery: true, }) ).toThrowError(); }); diff --git a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts index e4413f3be7fe2..55d0c30b58e34 100644 --- a/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts +++ b/packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts @@ -23,7 +23,7 @@ export interface FieldValueCountsParams { values: FieldHitValue[]; field: DataViewField; count?: number; - isTextBased: boolean; + isEsqlQuery: boolean; } export function getFieldExampleBuckets(params: FieldValueCountsParams, formatter?: FieldFormat) { @@ -31,7 +31,7 @@ export function getFieldExampleBuckets(params: FieldValueCountsParams, formatter count: DEFAULT_SIMPLE_EXAMPLES_SIZE, }); - if (!canProvideExamplesForField(params.field, params.isTextBased)) { + if (!canProvideExamplesForField(params.field, params.isEsqlQuery)) { throw new Error( `Analysis is not available this field type: "${params.field.type}". Field name: "${params.field.name}"` ); diff --git a/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts b/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts index 309f5f054683b..57a7d0be8fda9 100644 --- a/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts +++ b/packages/kbn-unified-field-list/src/services/field_stats/field_stats_utils.ts @@ -416,7 +416,7 @@ export async function getSimpleExamples( values: getFieldValues(simpleExamplesResult.hits.hits, field, dataView), field, count: DEFAULT_SIMPLE_EXAMPLES_SIZE, - isTextBased: false, + isEsqlQuery: false, }, formatter ); diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts deleted file mode 100644 index 553fdd749941f..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import { buildSearchFilter, fetchAndCalculateFieldStats } from './field_stats_utils_text_based'; - -describe('fieldStatsUtilsTextBased', function () { - describe('buildSearchFilter()', () => { - it('should create a time range filter', () => { - expect( - buildSearchFilter({ - timeFieldName: 'timestamp', - fromDate: '2022-12-05T23:00:00.000Z', - toDate: '2023-01-05T09:33:05.359Z', - }) - ).toMatchInlineSnapshot(` - Object { - "range": Object { - "timestamp": Object { - "format": "strict_date_optional_time", - "gte": "2022-12-05T23:00:00.000Z", - "lte": "2023-01-05T09:33:05.359Z", - }, - }, - } - `); - }); - it('should not create a time range filter', () => { - expect( - buildSearchFilter({ - timeFieldName: undefined, - fromDate: '2022-12-05T23:00:00.000Z', - toDate: '2023-01-05T09:33:05.359Z', - }) - ).toBeNull(); - }); - }); - - describe('fetchAndCalculateFieldStats()', () => { - it('should provide top values', async () => { - const searchHandler = jest.fn().mockResolvedValue({ - values: [ - [3, 'a'], - [1, 'b'], - ], - }); - expect( - await fetchAndCalculateFieldStats({ - searchHandler, - esqlBaseQuery: 'from logs* | limit 1000', - field: { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - }) - ).toMatchInlineSnapshot(` - Object { - "sampledDocuments": 4, - "sampledValues": 4, - "topValues": Object { - "buckets": Array [ - Object { - "count": 3, - "key": "a", - }, - Object { - "count": 1, - "key": "b", - }, - ], - }, - "totalDocuments": 4, - } - `); - expect(searchHandler).toHaveBeenCalledWith( - expect.objectContaining({ - query: - 'from logs* | limit 1000\n| WHERE `message` IS NOT NULL\n | STATS `message_terms` = count(`message`) BY `message`\n | SORT `message_terms` DESC\n | LIMIT 10', - }) - ); - }); - - it('should provide text examples', async () => { - const searchHandler = jest.fn().mockResolvedValue({ - values: [[['programming', 'cool']], ['elastic', 'cool']], - }); - expect( - await fetchAndCalculateFieldStats({ - searchHandler, - esqlBaseQuery: 'from logs* | limit 1000', - field: { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, - }) - ).toMatchInlineSnapshot(` - Object { - "sampledDocuments": 2, - "sampledValues": 4, - "topValues": Object { - "areExamples": true, - "buckets": Array [ - Object { - "count": 2, - "key": "cool", - }, - Object { - "count": 1, - "key": "elastic", - }, - Object { - "count": 1, - "key": "programming", - }, - ], - }, - "totalDocuments": 2, - } - `); - - expect(searchHandler).toHaveBeenCalledWith( - expect.objectContaining({ - query: - 'from logs* | limit 1000\n| WHERE `message` IS NOT NULL\n | KEEP `message`\n | LIMIT 100', - }) - ); - }); - }); -}); diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts deleted file mode 100644 index b64d26b0cbb59..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/field_stats_utils_text_based.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { ESQLSearchResponse } from '@kbn/es-types'; -import { appendToESQLQuery } from '@kbn/esql-utils'; -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import type { FieldStatsResponse } from '../../types'; -import { - DEFAULT_TOP_VALUES_SIZE, - DEFAULT_SIMPLE_EXAMPLES_SIZE, - SIMPLE_EXAMPLES_FETCH_SIZE, -} from '../../constants'; -import { - canProvideStatsForFieldTextBased, - canProvideTopValuesForFieldTextBased, - canProvideExamplesForField, -} from '../../utils/can_provide_stats'; -import { getFieldExampleBuckets } from '../field_examples_calculator'; - -export type SearchHandlerTextBased = ({ query }: { query: string }) => Promise; - -export function buildSearchFilter({ - timeFieldName, - fromDate, - toDate, -}: { - timeFieldName?: string; - fromDate: string; - toDate: string; -}) { - return timeFieldName - ? { - range: { - [timeFieldName]: { - gte: fromDate, - lte: toDate, - format: 'strict_date_optional_time', - }, - }, - } - : null; -} - -interface FetchAndCalculateFieldStatsParams { - searchHandler: SearchHandlerTextBased; - field: DataViewField; - esqlBaseQuery: string; -} - -export async function fetchAndCalculateFieldStats(params: FetchAndCalculateFieldStatsParams) { - const { field } = params; - if (!canProvideStatsForFieldTextBased(field)) { - return {}; - } - if (field.type === 'boolean') { - return await getStringTopValues(params, 3); - } - if (canProvideTopValuesForFieldTextBased(field)) { - return await getStringTopValues(params); - } - if (canProvideExamplesForField(field, true)) { - return await getSimpleTextExamples(params); - } - - return {}; -} - -export async function getStringTopValues( - params: FetchAndCalculateFieldStatsParams, - size = DEFAULT_TOP_VALUES_SIZE -): Promise> { - const { searchHandler, field, esqlBaseQuery } = params; - const safeEsqlFieldName = getSafeESQLFieldName(field.name); - const safeEsqlFieldNameTerms = getSafeESQLFieldName(`${field.name}_terms`); - const esqlQuery = appendToESQLQuery( - esqlBaseQuery, - `| WHERE ${safeEsqlFieldName} IS NOT NULL - | STATS ${safeEsqlFieldNameTerms} = count(${safeEsqlFieldName}) BY ${safeEsqlFieldName} - | SORT ${safeEsqlFieldNameTerms} DESC - | LIMIT ${size}` - ); - - const result = await searchHandler({ query: esqlQuery }); - const values = result?.values as Array<[number, string]>; - - if (!values?.length) { - return {}; - } - - const sampledValues = values?.reduce((acc: number, row) => acc + row[0], 0); - - const topValues = { - buckets: values.map((value) => ({ - count: value[0], - key: value[1], - })), - }; - - return { - totalDocuments: sampledValues, - sampledDocuments: sampledValues, - sampledValues, - topValues, - }; -} - -export async function getSimpleTextExamples( - params: FetchAndCalculateFieldStatsParams -): Promise> { - const { searchHandler, field, esqlBaseQuery } = params; - const safeEsqlFieldName = getSafeESQLFieldName(field.name); - const esqlQuery = appendToESQLQuery( - esqlBaseQuery, - `| WHERE ${safeEsqlFieldName} IS NOT NULL - | KEEP ${safeEsqlFieldName} - | LIMIT ${SIMPLE_EXAMPLES_FETCH_SIZE}` - ); - - const result = await searchHandler({ query: esqlQuery }); - const values = ((result?.values as Array<[string | string[]]>) || []).map((value) => - Array.isArray(value) && value.length === 1 ? value[0] : value - ); - - if (!values?.length) { - return {}; - } - - const sampledDocuments = values?.length; - - const fieldExampleBuckets = getFieldExampleBuckets({ - values, - field, - count: DEFAULT_SIMPLE_EXAMPLES_SIZE, - isTextBased: true, - }); - - return { - totalDocuments: sampledDocuments, - sampledDocuments: fieldExampleBuckets.sampledDocuments, - sampledValues: fieldExampleBuckets.sampledValues, - topValues: { - buckets: fieldExampleBuckets.buckets, - areExamples: true, - }, - }; -} - -function getSafeESQLFieldName(str: string): string { - return `\`${str}\``; -} diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts deleted file mode 100644 index 8915a30bf4f41..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { loadFieldStatsTextBased } from './load_field_stats_text_based'; diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts deleted file mode 100644 index 5f77f15906896..0000000000000 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { AggregateQuery } from '@kbn/es-query'; -import { getESQLWithSafeLimit, getESQLResults } from '@kbn/esql-utils'; -import type { FieldStatsResponse } from '../../types'; -import { - buildSearchFilter, - SearchHandlerTextBased, - fetchAndCalculateFieldStats, -} from './field_stats_utils_text_based'; -import { ESQL_SAFE_LIMIT } from '../../constants'; - -interface FetchFieldStatsParamsTextBased { - services: { - data: DataPublicPluginStart; - }; - dataView: DataView; - field: DataViewField; - fromDate: string; - toDate: string; - baseQuery: AggregateQuery; - abortController?: AbortController; -} - -export type LoadFieldStatsTextBasedHandler = ( - params: FetchFieldStatsParamsTextBased -) => Promise>; - -/** - * Loads and aggregates stats data for an ES|QL query field - * @param services - * @param dataView - * @param field - * @param fromDate - * @param toDate - * @param baseQuery - * @param abortController - */ -export const loadFieldStatsTextBased: LoadFieldStatsTextBasedHandler = async ({ - services, - dataView, - field, - fromDate, - toDate, - baseQuery, - abortController, -}) => { - const { data } = services; - - try { - if (!dataView?.id || !field?.type) { - return {}; - } - - const searchHandler: SearchHandlerTextBased = async ({ query }) => { - const filter = buildSearchFilter({ timeFieldName: dataView.timeFieldName, fromDate, toDate }); - const result = await getESQLResults({ - esqlQuery: query, - filter, - search: data.search.search, - signal: abortController?.signal, - timeRange: { from: fromDate, to: toDate }, - }); - return result.response; - }; - - if (!('esql' in baseQuery)) { - throw new Error('query must be of type AggregateQuery'); - } - - return await fetchAndCalculateFieldStats({ - searchHandler, - field, - esqlBaseQuery: getESQLWithSafeLimit(baseQuery.esql, ESQL_SAFE_LIMIT), - }); - } catch (error) { - // console.error(error); - throw new Error('Could not provide field stats', { cause: error }); - } -}; diff --git a/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts b/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts index 297e1e26c8c56..c27a44494a3e7 100644 --- a/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts +++ b/packages/kbn-unified-field-list/src/utils/can_provide_stats.test.ts @@ -10,7 +10,7 @@ import { canProvideStatsForField, canProvideExamplesForField, - canProvideStatsForFieldTextBased, + canProvideStatsForEsqlField, } from './can_provide_stats'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; @@ -34,40 +34,12 @@ describe('can_provide_stats', function () { ); }); - it('works for text based columns', function () { + it('should not work for ES|QL columns', function () { expect( canProvideStatsForField( { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, true ) - ).toBe(true); - expect( - canProvideStatsForField( - { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); - expect( - canProvideStatsForField({ name: 'message', type: 'number' } as DataViewField, true) - ).toBe(true); - expect( - canProvideStatsForField({ name: 'message', type: 'boolean' } as DataViewField, true) - ).toBe(true); - expect(canProvideStatsForField({ name: 'message', type: 'ip' } as DataViewField, true)).toBe( - true - ); - expect( - canProvideStatsForField({ name: 'message', type: 'geo_point' } as DataViewField, true) - ).toBe(true); - expect( - canProvideStatsForField( - { name: '_id', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); - - expect( - canProvideStatsForField({ name: 'message', type: 'date' } as DataViewField, true) ).toBe(false); }); }); @@ -82,83 +54,24 @@ describe('can_provide_stats', function () { ); }); - it('works for text based columns', function () { + it('should not work for ES|QL columns', function () { expect( canProvideExamplesForField( { name: 'message', type: 'string', esTypes: ['text'] } as DataViewField, true ) - ).toBe(true); - expect( - canProvideExamplesForField( - { name: 'message', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'number' } as DataViewField, true) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'boolean' } as DataViewField, true) ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'ip' } as DataViewField, true) - ).toBe(false); - expect( - canProvideExamplesForField({ name: 'message', type: 'geo_point' } as DataViewField, true) - ).toBe(true); - expect( - canProvideExamplesForField({ name: 'message', type: 'date' } as DataViewField, true) - ).toBe(false); - expect( - canProvideStatsForField( - { name: '_id', type: 'string', esTypes: ['keyword'] } as DataViewField, - true - ) - ).toBe(true); }); - describe('canProvideStatsForFieldTextBased', function () { - it('works for text based columns', function () { + describe('canProvideStatsForEsqlField', function () { + it('should not work for ES|QL columns', function () { expect( - canProvideStatsForFieldTextBased({ + canProvideStatsForEsqlField({ name: 'message', type: 'string', esTypes: ['text'], } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ - name: 'message', - type: 'string', - esTypes: ['keyword'], - } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'number' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'boolean' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'ip' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'ip_range' } as DataViewField) ).toBe(false); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'geo_point' } as DataViewField) - ).toBe(true); - expect( - canProvideStatsForFieldTextBased({ name: 'message', type: 'date' } as DataViewField) - ).toBe(false); - expect( - canProvideStatsForFieldTextBased({ - name: '_id', - type: 'string', - esTypes: ['keyword'], - } as DataViewField) - ).toBe(true); }); }); }); diff --git a/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts b/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts index e84137fa17f2e..c3fd9734de5e3 100644 --- a/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts +++ b/packages/kbn-unified-field-list/src/utils/can_provide_stats.ts @@ -9,22 +9,22 @@ import type { DataViewField } from '@kbn/data-views-plugin/common'; -export function canProvideStatsForField(field: DataViewField, isTextBased: boolean): boolean { - if (isTextBased) { - return canProvideStatsForFieldTextBased(field); +export function canProvideStatsForField(field: DataViewField, isEsqlQuery: boolean): boolean { + if (isEsqlQuery) { + return false; } return ( - (field.aggregatable && canProvideAggregatedStatsForField(field, isTextBased)) || + (field.aggregatable && canProvideAggregatedStatsForField(field, isEsqlQuery)) || ((!field.aggregatable || field.type === 'geo_point' || field.type === 'geo_shape') && - canProvideExamplesForField(field, isTextBased)) + canProvideExamplesForField(field, isEsqlQuery)) ); } export function canProvideAggregatedStatsForField( field: DataViewField, - isTextBased: boolean + isEsqlQuery: boolean ): boolean { - if (isTextBased) { + if (isEsqlQuery) { return false; } return !( @@ -39,20 +39,17 @@ export function canProvideAggregatedStatsForField( export function canProvideNumberSummaryForField( field: DataViewField, - isTextBased: boolean + isEsqlQuery: boolean ): boolean { - if (isTextBased) { + if (isEsqlQuery) { return false; } return field.timeSeriesMetric === 'counter'; } -export function canProvideExamplesForField(field: DataViewField, isTextBased: boolean): boolean { - if (isTextBased) { - return ( - (field.type === 'string' && !canProvideTopValuesForFieldTextBased(field)) || - ['geo_point', 'geo_shape'].includes(field.type) - ); +export function canProvideExamplesForField(field: DataViewField, isEsqlQuery: boolean): boolean { + if (isEsqlQuery) { + return false; } if (field.name === '_score') { return false; @@ -69,17 +66,6 @@ export function canProvideExamplesForField(field: DataViewField, isTextBased: bo ].includes(field.type); } -export function canProvideTopValuesForFieldTextBased(field: DataViewField): boolean { - if (field.name === '_id') { - return false; - } - const esTypes = field.esTypes?.[0]; - return ( - Boolean(field.type === 'string' && esTypes && ['keyword', 'version'].includes(esTypes)) || - ['keyword', 'version', 'ip', 'number', 'boolean'].includes(field.type) - ); -} - -export function canProvideStatsForFieldTextBased(field: DataViewField): boolean { - return canProvideTopValuesForFieldTextBased(field) || canProvideExamplesForField(field, true); +export function canProvideStatsForEsqlField(field: DataViewField): boolean { + return false; } diff --git a/packages/kbn-unified-field-list/tsconfig.json b/packages/kbn-unified-field-list/tsconfig.json index 830e56ac6ab00..54b67143b7c7b 100644 --- a/packages/kbn-unified-field-list/tsconfig.json +++ b/packages/kbn-unified-field-list/tsconfig.json @@ -32,7 +32,6 @@ "@kbn/shared-ux-button-toolbar", "@kbn/field-utils", "@kbn/visualization-utils", - "@kbn/esql-utils", "@kbn/search-types", "@kbn/fields-metadata-plugin", "@kbn/ui-theme" diff --git a/test/functional/apps/discover/group6/_sidebar_field_stats.ts b/test/functional/apps/discover/group6/_sidebar_field_stats.ts index 325adb313ed6c..e725606609996 100644 --- a/test/functional/apps/discover/group6/_sidebar_field_stats.ts +++ b/test/functional/apps/discover/group6/_sidebar_field_stats.ts @@ -155,77 +155,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await unifiedFieldList.waitUntilSidebarHasLoaded(); }); - it('should show top values popover for numeric field', async () => { + it('should not show top values popover for numeric field', async () => { await unifiedFieldList.clickFieldListItem('bytes'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(10); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '42 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('bytes', '0'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* METADATA _index, _id | sort @timestamp desc | limit 500\n| WHERE \`bytes\`==0` - ); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); - it('should show a top values popover for a keyword field', async () => { + it('should not show a top values popover for a keyword field', async () => { await unifiedFieldList.clickFieldListItem('extension.raw'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(5); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '500 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('extension.raw', 'css'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* METADATA _index, _id | sort @timestamp desc | limit 500\n| WHERE \`extension.raw\`=="css"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for an ip field', async () => { - await unifiedFieldList.clickFieldListItem('clientip'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(10); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '32 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('clientip', '216.126.255.31'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* METADATA _index, _id | sort @timestamp desc | limit 500\n| WHERE \`clientip\`::string=="216.126.255.31"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for _index field', async () => { - await unifiedFieldList.clickFieldListItem('_index'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(1); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '500 sample values' - ); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); @@ -240,102 +178,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await unifiedFieldList.closeFieldPopover(); }); - it('should show examples for geo points field', async () => { - await unifiedFieldList.clickFieldListItem('geo.coordinates'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(11); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show examples for text field', async () => { + it('should not show examples for text field', async () => { await unifiedFieldList.clickFieldListItem('extension'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(5); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - - await unifiedFieldList.clickFieldListPlusFilter('extension', 'css'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* METADATA _index, _id | sort @timestamp desc | limit 500\n| WHERE \`extension\`=="css"` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show examples for _id field', async () => { - await unifiedFieldList.clickFieldListItem('_id'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Examples'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(11); - await testSubjects.missingOrFail('unifiedFieldStats-buttonGroup'); - await testSubjects.missingOrFail('unifiedFieldStats-histogram'); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '100 sample records' - ); - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for a more complex query', async () => { - const testQuery = `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3`; - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await unifiedFieldList.waitUntilSidebarHasLoaded(); - - await unifiedFieldList.clickFieldListItem('avg(bytes)'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(3); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '3 sample values' - ); - - await unifiedFieldList.clickFieldListPlusFilter('avg(bytes)', '5453'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql( - `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| WHERE \`avg(bytes)\`==5453` - ); - - await unifiedFieldList.closeFieldPopover(); - }); - - it('should show a top values popover for a boolean field', async () => { - const testQuery = `row enabled = true`; - await monacoEditor.setCodeEditorValue(testQuery); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await unifiedFieldList.waitUntilSidebarHasLoaded(); - - await unifiedFieldList.clickFieldListItem('enabled'); - await testSubjects.existOrFail('dscFieldStats-topValues'); - expect(await testSubjects.getVisibleText('dscFieldStats-title')).to.be('Top values'); - const topValuesRows = await testSubjects.findAll('dscFieldStats-topValues-bucket'); - expect(topValuesRows.length).to.eql(1); - expect(await unifiedFieldList.getFieldStatsTopValueBucketsVisibleText()).to.be( - 'true\n100%' - ); - expect(await testSubjects.getVisibleText('dscFieldStats-statsFooter')).to.contain( - '1 sample value' - ); - - await unifiedFieldList.clickFieldListMinusFilter('enabled', 'true'); - const editorValue = await monacoEditor.getCodeEditorValue(); - expect(editorValue).to.eql(`row enabled = true\n| WHERE \`enabled\`!=true`); + await testSubjects.missingOrFail('dscFieldStats-statsFooter'); await unifiedFieldList.closeFieldPopover(); }); }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 0f5c34aaae9f6..e3201bc94972f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -8455,7 +8455,6 @@ "unifiedFieldList.fieldsAccordion.existenceTimeoutLabel": "Les informations de champ ont pris trop de temps", "unifiedFieldList.fieldStats.bucketPercentageTooltip": "{formattedPercentage} ({count, plural, one {# enregistrement} other {# enregistrements}})", "unifiedFieldList.fieldStats.calculatedFromSampleRecordsLabel": "Calculé à partir de {sampledDocumentsFormatted} {sampledDocuments, plural, one {exemple d'enregistrement} other {exemples d'enregistrement}}.", - "unifiedFieldList.fieldStats.calculatedFromSampleValuesLabel": "Calculé à partir {sampledValuesFormatted} {sampledValues, plural, one {d'un exemple de valeur} other {d’exemples de valeur}}.", "unifiedFieldList.fieldStats.calculatedFromTotalRecordsLabel": "Calculé à partir de {totalDocumentsFormatted} {totalDocuments, plural, one {enregistrement} other {enregistrements}}.", "unifiedFieldList.fieldStats.countLabel": "Décompte", "unifiedFieldList.fieldStats.displayToggleLegend": "Basculer soit", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 28840b8c7765c..7f32d93fc486b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8444,7 +8444,6 @@ "unifiedFieldList.fieldsAccordion.existenceTimeoutLabel": "フィールド情報に時間がかかりすぎました", "unifiedFieldList.fieldStats.bucketPercentageTooltip": "{formattedPercentage} ({count, plural, other {# レコード}})", "unifiedFieldList.fieldStats.calculatedFromSampleRecordsLabel": "{sampledDocumentsFormatted}サンプル{sampledDocuments, plural, other {レコード}}から計算されました。", - "unifiedFieldList.fieldStats.calculatedFromSampleValuesLabel": "{sampledValuesFormatted}サンプル{sampledValues, plural, other {値}}から計算されました。", "unifiedFieldList.fieldStats.calculatedFromTotalRecordsLabel": "{totalDocumentsFormatted} {totalDocuments, plural, other {レコード}}から計算されました。", "unifiedFieldList.fieldStats.countLabel": "カウント", "unifiedFieldList.fieldStats.displayToggleLegend": "次のどちらかを切り替えます:", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 918a37764c448..f6a0af9af8771 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8279,7 +8279,6 @@ "unifiedFieldList.fieldsAccordion.existenceTimeoutLabel": "字段信息花费时间过久", "unifiedFieldList.fieldStats.bucketPercentageTooltip": "{formattedPercentage}({count, plural, other {# 条记录}})", "unifiedFieldList.fieldStats.calculatedFromSampleRecordsLabel": "基于 {sampledDocumentsFormatted} 个样例{sampledDocuments, plural, other {记录}}计算。", - "unifiedFieldList.fieldStats.calculatedFromSampleValuesLabel": "基于 {sampledValuesFormatted} 个样例{sampledValues, plural, other {值}}计算。", "unifiedFieldList.fieldStats.calculatedFromTotalRecordsLabel": "基于 {totalDocumentsFormatted} 个样例{totalDocuments, plural, other {记录}}计算。", "unifiedFieldList.fieldStats.countLabel": "计数", "unifiedFieldList.fieldStats.displayToggleLegend": "切换",