diff --git a/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.test.ts b/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.test.ts index 3922e55c5523d..b3dc1683a24f3 100644 --- a/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.test.ts +++ b/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.test.ts @@ -185,6 +185,30 @@ describe('cascaded documents helpers utils', () => { expect(result.appliedFunctions).toEqual([]); }); + it('should return empty metadata when a group field is an inline cast wrapping an unsupported function', () => { + const queryString = `FROM kibana_sample_data_logs | STATS count = COUNT(*) BY ts = BUCKET(@timestamp, 1 hour)::string`; + + const result = getESQLStatsQueryMeta(queryString); + expect(result.groupByFields).toEqual([]); + expect(result.appliedFunctions).toEqual([]); + }); + + it('should return empty metadata when a group field among multiple is an inline cast wrapping an unsupported function', () => { + const queryString = `FROM kibana_sample_data_logs | STATS count = COUNT(*) BY agent.keyword, ts = BUCKET(@timestamp, 1 hour)::string`; + + const result = getESQLStatsQueryMeta(queryString); + expect(result.groupByFields).toEqual([]); + expect(result.appliedFunctions).toEqual([]); + }); + + it('should allow an inline cast of a simple column', () => { + const queryString = `FROM kibana_sample_data_logs | STATS count = COUNT(*) BY casted = clientip::keyword`; + + const result = getESQLStatsQueryMeta(queryString); + expect(result.groupByFields).toEqual([{ field: 'casted', type: 'inlineCast' }]); + expect(result.appliedFunctions).toEqual([{ identifier: 'count', aggregation: 'COUNT' }]); + }); + it('should return the appropriate metadata despite there being a keep, as long as it specifies the current group field', () => { const queryString = ` FROM kibana_sample_data_logs | STATS Visits = COUNT(), Unique = COUNT_DISTINCT(clientip), p95 = PERCENTILE(bytes, 95), median = MEDIAN(bytes) BY response.keyword | KEEP Visits, Unique, p95, median, response.keyword | LIMIT 123 diff --git a/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.ts b/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.ts index f98737c2ddc0e..f89574c10295f 100644 --- a/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.ts +++ b/src/platform/packages/shared/kbn-esql-utils/src/utils/cascaded_documents_helpers/index.ts @@ -23,12 +23,14 @@ import { type ESQLColumn, isBinaryExpression, Walker, + isInlineCast, } from '@kbn/esql-language'; import type { BinaryExpressionComparisonOperator, ESQLBinaryExpression, ESQLUnaryExpression, ESQLPostfixUnaryExpression, + ESQLProperNode, } from '@kbn/esql-language/src/types'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ESQLControlVariable } from '@kbn/esql-types'; @@ -53,6 +55,22 @@ import { isCategorizeFunctionWithFunctionArgument, } from './utils'; +const hasUnsupportedGroupingFunction = (definition: ESQLProperNode): boolean => { + const funcExpr = isFunctionExpression(definition) + ? definition + : isInlineCast(definition) && isFunctionExpression(definition.value) + ? definition.value + : null; + + if (!funcExpr) { + return false; + } + + return ( + !isSupportedStatsFunction(funcExpr.name) || isCategorizeFunctionWithFunctionArgument(funcExpr) + ); +}; + type NodeType = 'group' | 'leaf'; export interface AppliedStatsFunction { @@ -141,11 +159,8 @@ export const getESQLStatsQueryMeta = (queryString: string): ESQLStatsQueryMeta = } const groupFieldDefinition = getFieldDefinitionFromArg(groupFieldNode.arg); - if ( - isFunctionExpression(groupFieldDefinition) && - (!isSupportedStatsFunction(groupFieldDefinition.name) || - isCategorizeFunctionWithFunctionArgument(groupFieldDefinition)) - ) { + + if (hasUnsupportedGroupingFunction(groupFieldDefinition)) { // if the group field has a grouping function that is not supported, // this nullifies the entire query to count as a valid query for the cascade experience groupByFields.splice(0, groupByFields.length);