diff --git a/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.test.ts b/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.test.ts new file mode 100644 index 0000000000000..5f0076b329400 --- /dev/null +++ b/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.test.ts @@ -0,0 +1,33 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { getResponseAggId } from './get_response_agg_config_class'; + +describe('getResponseAggConfigClass', () => { + describe('getResponseAggId', () => { + it('should generate a dot-separated ID from parent/key', () => { + const id = getResponseAggId('parent', 'child'); + expect(id).toBe('parent.child'); + }); + + it('should use brackets/quotes if the value includes a dot', () => { + const id = getResponseAggId('parent', 'foo.bar'); + expect(id).toBe(`parent['foo.bar']`); + }); + + it('should escape quotes', () => { + const id = getResponseAggId('parent', `foo.b'ar`); + expect(id).toBe(`parent['foo.b\\'ar']`); + }); + + it('should escape backslashes', () => { + const id = getResponseAggId('parent', `f\\oo.b'ar`); + expect(id).toBe(`parent['f\\\\oo.b\\'ar']`); + }); + }); +}); diff --git a/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts b/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts index 2b582bfa94192..cd2c5c7f90bac 100644 --- a/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts +++ b/src/plugins/data/common/search/aggs/metrics/lib/get_response_agg_config_class.ts @@ -30,6 +30,15 @@ export interface IResponseAggConfig extends IMetricAggConfig { parentId: IMetricAggConfig['id']; } +export function getResponseAggId(parentId: string, key: string) { + const subId = String(key); + if (subId.indexOf('.') > -1) { + return parentId + "['" + subId.replace(/[\\']/g, '\\$&') + "']"; // $& means the whole matched string + } else { + return parentId + '.' + subId; + } +} + export const create = (parentAgg: IMetricAggConfig, props: Partial) => { /** * AggConfig "wrapper" for multi-value metric aggs which @@ -40,17 +49,7 @@ export const create = (parentAgg: IMetricAggConfig, props: Partial -1) { - id = parentId + "['" + subId.replace(/'/g, "\\'") + "']"; - } else { - id = parentId + '.' + subId; - } - - this.id = id; + this.id = getResponseAggId(parentId, key); this.key = key; this.parentId = parentId; }