diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md
deleted file mode 100644
index 2ef8c797f4054..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md)
-
-## AggConfigOptions.enabled property
-
-Signature:
-
-```typescript
-enabled?: boolean;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md
deleted file mode 100644
index 8939854ab19ca..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.id.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md)
-
-## AggConfigOptions.id property
-
-Signature:
-
-```typescript
-id?: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md
index b841d9b04d6a7..ff8055b8cf1b1 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.md
@@ -2,21 +2,12 @@
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md)
-## AggConfigOptions interface
+## AggConfigOptions type
Signature:
```typescript
-export interface AggConfigOptions
+export declare type AggConfigOptions = Assign;
```
-
-## Properties
-
-| Property | Type | Description |
-| --- | --- | --- |
-| [enabled](./kibana-plugin-plugins-data-public.aggconfigoptions.enabled.md) | boolean | |
-| [id](./kibana-plugin-plugins-data-public.aggconfigoptions.id.md) | string | |
-| [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md) | Record<string, any> | |
-| [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md) | string | |
-| [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md) | IAggType | |
-
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md
deleted file mode 100644
index 45219a837cc33..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.params.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [params](./kibana-plugin-plugins-data-public.aggconfigoptions.params.md)
-
-## AggConfigOptions.params property
-
-Signature:
-
-```typescript
-params?: Record;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md
deleted file mode 100644
index b2b42eb2e5b4d..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.schema.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [schema](./kibana-plugin-plugins-data-public.aggconfigoptions.schema.md)
-
-## AggConfigOptions.schema property
-
-Signature:
-
-```typescript
-schema?: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md
deleted file mode 100644
index 866065ce52ba6..0000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigoptions.type.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) > [type](./kibana-plugin-plugins-data-public.aggconfigoptions.type.md)
-
-## AggConfigOptions.type property
-
-Signature:
-
-```typescript
-type: IAggType;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.makeagg.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.makeagg.md
index 43f30d73ca6df..a91db7e7aac8b 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.makeagg.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.makeagg.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-makeAgg: (agg: TAggConfig, state?: any) => TAggConfig;
+makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.md
index b75065da91abd..f9733529a315d 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggparamtype.md
@@ -21,5 +21,5 @@ export declare class AggParamType ex
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [allowedAggs](./kibana-plugin-plugins-data-public.aggparamtype.allowedaggs.md) | | string[] | |
-| [makeAgg](./kibana-plugin-plugins-data-public.aggparamtype.makeagg.md) | | (agg: TAggConfig, state?: any) => TAggConfig | |
+| [makeAgg](./kibana-plugin-plugins-data-public.aggparamtype.makeagg.md) | | (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig | |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index 457d8595086ab..4f780673f011f 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -49,7 +49,6 @@
| Interface | Description |
| --- | --- |
-| [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | |
| [AggParamOption](./kibana-plugin-plugins-data-public.aggparamoption.md) | |
| [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | |
| [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | |
@@ -118,6 +117,7 @@
| Type Alias | Description |
| --- | --- |
+| [AggConfigOptions](./kibana-plugin-plugins-data-public.aggconfigoptions.md) | |
| [AggParam](./kibana-plugin-plugins-data-public.aggparam.md) | |
| [CustomFilter](./kibana-plugin-plugins-data-public.customfilter.md) | |
| [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | |
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 49ef1000d7993..924fcd6730f93 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -139,6 +139,7 @@ export class DataPublicPlugin implements Plugin;
- // (undocumented)
- schema?: string;
- // (undocumented)
+export type AggConfigOptions = Assign;
// Warning: (ae-missing-release-tag) "AggGroupNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -112,7 +105,7 @@ export class AggParamType extends Ba
// (undocumented)
allowedAggs: string[];
// (undocumented)
- makeAgg: (agg: TAggConfig, state?: any) => TAggConfig;
+ makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
}
// Warning: (ae-missing-release-tag) "AggTypeFieldFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts
index 2813e3b9c5373..b5df90313230c 100644
--- a/src/plugins/data/public/search/aggs/agg_config.test.ts
+++ b/src/plugins/data/public/search/aggs/agg_config.test.ts
@@ -24,18 +24,21 @@ import { AggConfigs, CreateAggConfigParams } from './agg_configs';
import { AggType } from './agg_type';
import { AggTypesRegistryStart } from './agg_types_registry';
import { mockDataServices, mockAggTypesRegistry } from './test_helpers';
+import { MetricAggType } from './metrics/metric_agg_type';
import { Field as IndexPatternField, IndexPattern } from '../../index_patterns';
import { stubIndexPatternWithFields } from '../../../public/stubs';
+import { FieldFormatsStart } from '../../field_formats';
import { fieldFormatsServiceMock } from '../../field_formats/mocks';
describe('AggConfig', () => {
let indexPattern: IndexPattern;
let typesRegistry: AggTypesRegistryStart;
- const fieldFormats = fieldFormatsServiceMock.createStartContract();
+ let fieldFormats: FieldFormatsStart;
beforeEach(() => {
jest.restoreAllMocks();
mockDataServices();
+ fieldFormats = fieldFormatsServiceMock.createStartContract();
indexPattern = stubIndexPatternWithFields as IndexPattern;
typesRegistry = mockAggTypesRegistry();
});
@@ -325,7 +328,7 @@ describe('AggConfig', () => {
});
});
- describe('#toJSON', () => {
+ describe('#serialize', () => {
it('includes the aggs id, params, type and schema', () => {
const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
const configStates = {
@@ -342,7 +345,7 @@ describe('AggConfig', () => {
expect(aggConfig.type).toHaveProperty('name', 'date_histogram');
expect(typeof aggConfig.schema).toBe('string');
- const state = aggConfig.toJSON();
+ const state = aggConfig.serialize();
expect(state).toHaveProperty('id', '1');
expect(typeof state.params).toBe('object');
expect(state).toHaveProperty('type', 'date_histogram');
@@ -367,6 +370,201 @@ describe('AggConfig', () => {
});
});
+ describe('#toExpressionAst', () => {
+ beforeEach(() => {
+ fieldFormats.getDefaultInstance = (() => ({
+ getConverterFor: (t?: string) => t || identity,
+ })) as any;
+ indexPattern.fields.getByName = name =>
+ ({
+ format: {
+ getConverterFor: (t?: string) => t || identity,
+ },
+ } as IndexPatternField);
+ });
+
+ it('works with primitive param types', () => {
+ const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
+ const configStates = {
+ enabled: true,
+ type: 'terms',
+ schema: 'segment',
+ params: {
+ field: 'machine.os.keyword',
+ order: 'asc',
+ },
+ };
+ const aggConfig = ac.createAggConfig(configStates);
+ expect(aggConfig.toExpressionAst()).toMatchInlineSnapshot(`
+ Object {
+ "arguments": Object {
+ "enabled": Array [
+ true,
+ ],
+ "id": Array [
+ "1",
+ ],
+ "missingBucket": Array [
+ false,
+ ],
+ "missingBucketLabel": Array [
+ "Missing",
+ ],
+ "order": Array [
+ "asc",
+ ],
+ "otherBucket": Array [
+ false,
+ ],
+ "otherBucketLabel": Array [
+ "Other",
+ ],
+ "schema": Array [
+ "segment",
+ ],
+ "size": Array [
+ 5,
+ ],
+ },
+ "function": "aggTerms",
+ "type": "function",
+ }
+ `);
+ });
+
+ it('creates a subexpression for params of type "agg"', () => {
+ const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
+ const configStates = {
+ type: 'terms',
+ params: {
+ field: 'machine.os.keyword',
+ order: 'asc',
+ orderAgg: {
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'bytes',
+ order: 'asc',
+ size: 5,
+ },
+ },
+ },
+ };
+ const aggConfig = ac.createAggConfig(configStates);
+ const aggArg = aggConfig.toExpressionAst()?.arguments.orderAgg;
+ expect(aggArg).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "enabled": Array [
+ true,
+ ],
+ "id": Array [
+ "1-orderAgg",
+ ],
+ "missingBucket": Array [
+ false,
+ ],
+ "missingBucketLabel": Array [
+ "Missing",
+ ],
+ "order": Array [
+ "asc",
+ ],
+ "otherBucket": Array [
+ false,
+ ],
+ "otherBucketLabel": Array [
+ "Other",
+ ],
+ "schema": Array [
+ "orderAgg",
+ ],
+ "size": Array [
+ 5,
+ ],
+ },
+ "function": "aggTerms",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ]
+ `);
+ });
+
+ it('creates a subexpression for param types other than "agg" which have specified toExpressionAst', () => {
+ // Overwrite the `ranges` param in the `range` agg with a mock toExpressionAst function
+ const range: MetricAggType = typesRegistry.get('range');
+ range.expressionName = 'aggRange';
+ const rangesParam = range.params.find(p => p.name === 'ranges');
+ rangesParam!.toExpressionAst = (val: any) => ({
+ type: 'function',
+ function: 'aggRanges',
+ arguments: {
+ ranges: ['oh hi there!'],
+ },
+ });
+
+ const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
+ const configStates = {
+ type: 'range',
+ params: {
+ field: 'bytes',
+ },
+ };
+
+ const aggConfig = ac.createAggConfig(configStates);
+ const ranges = aggConfig.toExpressionAst()!.arguments.ranges;
+ expect(ranges).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "ranges": Array [
+ "oh hi there!",
+ ],
+ },
+ "function": "aggRanges",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ]
+ `);
+ });
+
+ it('stringifies any other params which are an object', () => {
+ const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
+ const configStates = {
+ type: 'terms',
+ params: {
+ field: 'machine.os.keyword',
+ order: 'asc',
+ json: { foo: 'bar' },
+ },
+ };
+ const aggConfig = ac.createAggConfig(configStates);
+ const json = aggConfig.toExpressionAst()?.arguments.json;
+ expect(json).toEqual([JSON.stringify(configStates.params.json)]);
+ });
+
+ it(`returns undefined if an expressionName doesn't exist on the agg type`, () => {
+ const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats });
+ const configStates = {
+ type: 'unknown type',
+ params: {},
+ };
+ const aggConfig = ac.createAggConfig(configStates);
+ expect(aggConfig.toExpressionAst()).toBe(undefined);
+ });
+ });
+
describe('#makeLabel', () => {
let aggConfig: AggConfig;
@@ -422,6 +620,9 @@ describe('AggConfig', () => {
let aggConfig: AggConfig;
beforeEach(() => {
+ fieldFormats.getDefaultInstance = (() => ({
+ getConverterFor: (t?: string) => t || identity,
+ })) as any;
indexPattern.fields.getByName = name =>
({
format: {
@@ -434,11 +635,7 @@ describe('AggConfig', () => {
type: 'histogram',
schema: 'bucket',
params: {
- field: {
- format: {
- getConverterFor: (t?: string) => t || identity,
- },
- },
+ field: 'bytes',
},
};
const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry, fieldFormats });
@@ -446,6 +643,11 @@ describe('AggConfig', () => {
});
it("returns the field's formatter", () => {
+ aggConfig.params.field = {
+ format: {
+ getConverterFor: (t?: string) => t || identity,
+ },
+ };
expect(aggConfig.fieldFormatter().toString()).toBe(
aggConfig
.getField()
diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts
index 6188849e0e6d4..973c69e3d4f5f 100644
--- a/src/plugins/data/public/search/aggs/agg_config.ts
+++ b/src/plugins/data/public/search/aggs/agg_config.ts
@@ -19,6 +19,8 @@
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
+import { Assign } from '@kbn/utility-types';
+import { ExpressionAstFunction, ExpressionAstArgument } from 'src/plugins/expressions/public';
import { IAggType } from './agg_type';
import { writeParams } from './agg_params';
import { IAggConfigs } from './agg_configs';
@@ -27,11 +29,17 @@ import { ISearchSource } from '../search_source';
import { FieldFormatsContentType, KBN_FIELD_TYPES } from '../../../common';
import { FieldFormatsStart } from '../../field_formats';
-export interface AggConfigOptions {
- type: IAggType;
+type State = string | number | boolean | null | undefined | SerializableState;
+
+interface SerializableState {
+ [key: string]: State | State[];
+}
+
+export interface AggConfigSerialized {
+ type: string;
enabled?: boolean;
id?: string;
- params?: Record;
+ params?: SerializableState;
schema?: string;
}
@@ -39,6 +47,8 @@ export interface AggConfigDependencies {
fieldFormats: FieldFormatsStart;
}
+export type AggConfigOptions = Assign;
+
/**
* @name AggConfig
*
@@ -257,7 +267,10 @@ export class AggConfig {
return configDsl;
}
- toJSON() {
+ /**
+ * @returns Returns a serialized representation of an AggConfig.
+ */
+ serialize(): AggConfigSerialized {
const params = this.params;
const outParams = _.transform(
@@ -281,7 +294,64 @@ export class AggConfig {
enabled: this.enabled,
type: this.type && this.type.name,
schema: this.schema,
- params: outParams,
+ params: outParams as SerializableState,
+ };
+ }
+
+ /**
+ * @deprecated - Use serialize() instead.
+ */
+ toJSON(): AggConfigSerialized {
+ return this.serialize();
+ }
+
+ /**
+ * @returns Returns an ExpressionAst representing the function for this agg type.
+ */
+ toExpressionAst(): ExpressionAstFunction | undefined {
+ const functionName = this.type && this.type.expressionName;
+ const { type, ...rest } = this.serialize();
+ if (!functionName || !rest.params) {
+ // Return undefined - there is no matching expression function for this agg
+ return;
+ }
+
+ // Go through each of the params and convert to an array of expression args.
+ const params = Object.entries(rest.params).reduce((acc, [key, value]) => {
+ const deserializedParam = this.getAggParams().find(p => p.name === key);
+
+ if (deserializedParam && deserializedParam.toExpressionAst) {
+ // If the param provides `toExpressionAst`, we call it with the value
+ const paramExpressionAst = deserializedParam.toExpressionAst(this.getParam(key));
+ if (paramExpressionAst) {
+ acc[key] = [
+ {
+ type: 'expression',
+ chain: [paramExpressionAst],
+ },
+ ];
+ }
+ } else if (typeof value === 'object') {
+ // For object params which don't provide `toExpressionAst`, we stringify
+ acc[key] = [JSON.stringify(value)];
+ } else if (typeof value !== 'undefined') {
+ // Everything else just gets stored in an array if it is defined
+ acc[key] = [value];
+ }
+
+ return acc;
+ }, {} as Record);
+
+ return {
+ type: 'function',
+ function: functionName,
+ arguments: {
+ ...params,
+ // Expression args which are provided to all functions
+ id: [this.id],
+ enabled: [this.enabled],
+ ...(this.schema ? { schema: [this.schema] } : {}), // schema may be undefined
+ },
};
}
diff --git a/src/plugins/data/public/search/aggs/agg_configs.ts b/src/plugins/data/public/search/aggs/agg_configs.ts
index 5ad09f824d3e4..d2151a2c5ed4d 100644
--- a/src/plugins/data/public/search/aggs/agg_configs.ts
+++ b/src/plugins/data/public/search/aggs/agg_configs.ts
@@ -20,7 +20,7 @@
import _ from 'lodash';
import { Assign } from '@kbn/utility-types';
-import { AggConfig, AggConfigOptions, IAggConfig } from './agg_config';
+import { AggConfig, AggConfigSerialized, IAggConfig } from './agg_config';
import { IAggType } from './agg_type';
import { AggTypesRegistryStart } from './agg_types_registry';
import { AggGroupNames } from './agg_groups';
@@ -51,7 +51,7 @@ export interface AggConfigsOptions {
fieldFormats: FieldFormatsStart;
}
-export type CreateAggConfigParams = Assign;
+export type CreateAggConfigParams = Assign;
/**
* @name AggConfigs
diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts
index 70c116d560c6f..fb0cb609a08cf 100644
--- a/src/plugins/data/public/search/aggs/agg_type.ts
+++ b/src/plugins/data/public/search/aggs/agg_type.ts
@@ -39,6 +39,7 @@ export interface AggTypeConfig<
createFilter?: (aggConfig: TAggConfig, key: any, params?: any) => any;
type?: string;
dslName?: string;
+ expressionName?: string;
makeLabel?: ((aggConfig: TAggConfig) => string) | (() => string);
ordered?: any;
hasNoDsl?: boolean;
@@ -88,6 +89,14 @@ export class AggType<
* @type {string}
*/
dslName: string;
+ /**
+ * the name of the expression function that this aggType represents.
+ * TODO: this should probably be a required field.
+ *
+ * @property name
+ * @type {string}
+ */
+ expressionName?: string;
/**
* the user friendly name that will be shown in the ui for this aggType
*
@@ -219,6 +228,7 @@ export class AggType<
this.name = config.name;
this.type = config.type || 'metrics';
this.dslName = config.dslName || config.name;
+ this.expressionName = config.expressionName;
this.title = config.title;
this.makeLabel = config.makeLabel || constant(this.name);
this.ordered = config.ordered;
diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts
index 4b154c338d48c..da07f581c9274 100644
--- a/src/plugins/data/public/search/aggs/agg_types.ts
+++ b/src/plugins/data/public/search/aggs/agg_types.ts
@@ -37,6 +37,7 @@ import { getDerivativeMetricAgg } from './metrics/derivative';
import { getCumulativeSumMetricAgg } from './metrics/cumulative_sum';
import { getMovingAvgMetricAgg } from './metrics/moving_avg';
import { getSerialDiffMetricAgg } from './metrics/serial_diff';
+
import { getDateHistogramBucketAgg } from './buckets/date_histogram';
import { getHistogramBucketAgg } from './buckets/histogram';
import { getRangeBucketAgg } from './buckets/range';
@@ -103,3 +104,7 @@ export const getAggTypes = ({
getGeoTitleBucketAgg({ getInternalStartServices }),
],
});
+
+import { aggTerms } from './buckets/terms_fn';
+
+export const getAggTypesFunctions = () => [aggTerms];
diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts
index 698e0dfb1d340..a12a1d7ac2d3d 100644
--- a/src/plugins/data/public/search/aggs/buckets/terms.ts
+++ b/src/plugins/data/public/search/aggs/buckets/terms.ts
@@ -26,7 +26,7 @@ import {
isStringOrNumberType,
migrateIncludeExcludeFormat,
} from './migrate_include_exclude_format';
-import { IAggConfigs } from '../agg_configs';
+import { AggConfigSerialized, IAggConfigs } from '../types';
import { Adapters } from '../../../../../inspector/public';
import { ISearchSource } from '../../search_source';
@@ -63,10 +63,27 @@ export interface TermsBucketAggDependencies {
getInternalStartServices: GetInternalStartServicesFn;
}
+export interface AggParamsTerms {
+ field: string;
+ order: 'asc' | 'desc';
+ orderBy: string;
+ orderAgg?: AggConfigSerialized;
+ size?: number;
+ missingBucket?: boolean;
+ missingBucketLabel?: string;
+ otherBucket?: boolean;
+ otherBucketLabel?: string;
+ // advanced
+ exclude?: string;
+ include?: string;
+ json?: string;
+}
+
export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDependencies) =>
new BucketAggType(
{
name: BUCKET_TYPES.TERMS,
+ expressionName: 'aggTerms',
title: termsTitle,
makeLabel(agg) {
const params = agg.params;
@@ -154,8 +171,7 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe
type: 'agg',
allowedAggs: termsAggFilter,
default: null,
- makeAgg(termsAgg, state) {
- state = state || {};
+ makeAgg(termsAgg, state = { type: 'count' }) {
state.schema = 'orderAgg';
const orderAgg = termsAgg.aggConfigs.createAggConfig(state, {
addToAggConfigs: false,
diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts
new file mode 100644
index 0000000000000..f55f1de796013
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts
@@ -0,0 +1,164 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { functionWrapper } from '../test_helpers';
+import { aggTerms } from './terms_fn';
+
+describe('agg_expression_functions', () => {
+ describe('aggTerms', () => {
+ const fn = functionWrapper(aggTerms());
+
+ test('fills in defaults when only required args are provided', () => {
+ const actual = fn({
+ field: 'machine.os.keyword',
+ order: 'asc',
+ orderBy: '1',
+ });
+ expect(actual).toMatchInlineSnapshot(`
+ Object {
+ "type": "agg_type",
+ "value": Object {
+ "enabled": true,
+ "id": undefined,
+ "params": Object {
+ "exclude": undefined,
+ "field": "machine.os.keyword",
+ "include": undefined,
+ "json": undefined,
+ "missingBucket": false,
+ "missingBucketLabel": "Missing",
+ "order": "asc",
+ "orderAgg": undefined,
+ "orderBy": "1",
+ "otherBucket": false,
+ "otherBucketLabel": "Other",
+ "size": 5,
+ },
+ "schema": undefined,
+ "type": "terms",
+ },
+ }
+ `);
+ });
+
+ test('includes optional params when they are provided', () => {
+ const actual = fn({
+ id: '1',
+ enabled: false,
+ schema: 'whatever',
+ field: 'machine.os.keyword',
+ order: 'desc',
+ orderBy: '2',
+ size: 6,
+ missingBucket: true,
+ missingBucketLabel: 'missing',
+ otherBucket: true,
+ otherBucketLabel: 'other',
+ exclude: 'ios',
+ });
+
+ expect(actual.value).toMatchInlineSnapshot(`
+ Object {
+ "enabled": false,
+ "id": "1",
+ "params": Object {
+ "exclude": "ios",
+ "field": "machine.os.keyword",
+ "include": undefined,
+ "json": undefined,
+ "missingBucket": true,
+ "missingBucketLabel": "missing",
+ "order": "desc",
+ "orderAgg": undefined,
+ "orderBy": "2",
+ "otherBucket": true,
+ "otherBucketLabel": "other",
+ "size": 6,
+ },
+ "schema": "whatever",
+ "type": "terms",
+ }
+ `);
+ });
+
+ test('handles orderAgg as a subexpression', () => {
+ const actual = fn({
+ field: 'machine.os.keyword',
+ order: 'asc',
+ orderBy: '1',
+ orderAgg: fn({ field: 'name', order: 'asc', orderBy: '1' }),
+ });
+
+ expect(actual.value.params).toMatchInlineSnapshot(`
+ Object {
+ "exclude": undefined,
+ "field": "machine.os.keyword",
+ "include": undefined,
+ "json": undefined,
+ "missingBucket": false,
+ "missingBucketLabel": "Missing",
+ "order": "asc",
+ "orderAgg": Object {
+ "enabled": true,
+ "id": undefined,
+ "params": Object {
+ "exclude": undefined,
+ "field": "name",
+ "include": undefined,
+ "json": undefined,
+ "missingBucket": false,
+ "missingBucketLabel": "Missing",
+ "order": "asc",
+ "orderAgg": undefined,
+ "orderBy": "1",
+ "otherBucket": false,
+ "otherBucketLabel": "Other",
+ "size": 5,
+ },
+ "schema": undefined,
+ "type": "terms",
+ },
+ "orderBy": "1",
+ "otherBucket": false,
+ "otherBucketLabel": "Other",
+ "size": 5,
+ }
+ `);
+ });
+
+ test('correctly parses json string argument', () => {
+ const actual = fn({
+ field: 'machine.os.keyword',
+ order: 'asc',
+ orderBy: '1',
+ json: '{ "foo": true }',
+ });
+
+ expect(actual.value.params.json).toEqual({ foo: true });
+ expect(() => {
+ fn({
+ field: 'machine.os.keyword',
+ order: 'asc',
+ orderBy: '1',
+ json: '/// intentionally malformed json ///',
+ });
+ }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`);
+ });
+ });
+});
diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.ts b/src/plugins/data/public/search/aggs/buckets/terms_fn.ts
new file mode 100644
index 0000000000000..7980bfabe79fb
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/buckets/terms_fn.ts
@@ -0,0 +1,181 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { Assign } from '@kbn/utility-types';
+import { ExpressionFunctionDefinition } from '../../../../../expressions/public';
+import { AggExpressionType, AggExpressionFunctionArgs } from '../';
+
+const aggName = 'terms';
+const fnName = 'aggTerms';
+
+type Input = any;
+type AggArgs = AggExpressionFunctionArgs;
+// Since the orderAgg param is an agg nested in a subexpression, we need to
+// overwrite the param type to expect a value of type AggExpressionType.
+type Arguments = AggArgs &
+ Assign<
+ AggArgs,
+ { orderAgg?: AggArgs['orderAgg'] extends undefined ? undefined : AggExpressionType }
+ >;
+type Output = AggExpressionType;
+type FunctionDefinition = ExpressionFunctionDefinition;
+
+export const aggTerms = (): FunctionDefinition => ({
+ name: fnName,
+ help: i18n.translate('data.search.aggs.function.buckets.terms.help', {
+ defaultMessage: 'Generates a serialized agg config for a terms agg',
+ }),
+ type: 'agg_type',
+ args: {
+ id: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.id.help', {
+ defaultMessage: 'ID for this aggregation',
+ }),
+ },
+ enabled: {
+ types: ['boolean'],
+ default: true,
+ help: i18n.translate('data.search.aggs.buckets.terms.enabled.help', {
+ defaultMessage: 'Specifies whether this aggregation should be enabled',
+ }),
+ },
+ schema: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.schema.help', {
+ defaultMessage: 'Schema to use for this aggregation',
+ }),
+ },
+ field: {
+ types: ['string'],
+ required: true,
+ help: i18n.translate('data.search.aggs.buckets.terms.field.help', {
+ defaultMessage: 'Field to use for this aggregation',
+ }),
+ },
+ order: {
+ types: ['string'],
+ required: true,
+ help: i18n.translate('data.search.aggs.buckets.terms.order.help', {
+ defaultMessage: 'Order in which to return the results: asc or desc',
+ }),
+ },
+ orderBy: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.orderBy.help', {
+ defaultMessage: 'Field to order results by',
+ }),
+ },
+ orderAgg: {
+ types: ['agg_type'],
+ help: i18n.translate('data.search.aggs.buckets.terms.orderAgg.help', {
+ defaultMessage: 'Agg config to use for ordering results',
+ }),
+ },
+ size: {
+ types: ['number'],
+ default: 5,
+ help: i18n.translate('data.search.aggs.buckets.terms.size.help', {
+ defaultMessage: 'Max number of buckets to retrieve',
+ }),
+ },
+ missingBucket: {
+ types: ['boolean'],
+ default: false,
+ help: i18n.translate('data.search.aggs.buckets.terms.missingBucket.help', {
+ defaultMessage: 'When set to true, groups together any buckets with missing fields',
+ }),
+ },
+ missingBucketLabel: {
+ types: ['string'],
+ default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', {
+ defaultMessage: 'Missing',
+ description: `Default label used in charts when documents are missing a field.
+ Visible when you create a chart with a terms aggregation and enable "Show missing values"`,
+ }),
+ help: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel.help', {
+ defaultMessage: 'Default label used in charts when documents are missing a field.',
+ }),
+ },
+ otherBucket: {
+ types: ['boolean'],
+ default: false,
+ help: i18n.translate('data.search.aggs.buckets.terms.otherBucket.help', {
+ defaultMessage: 'When set to true, groups together any buckets beyond the allowed size',
+ }),
+ },
+ otherBucketLabel: {
+ types: ['string'],
+ default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', {
+ defaultMessage: 'Other',
+ }),
+ help: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel.help', {
+ defaultMessage: 'Default label used in charts for documents in the Other bucket',
+ }),
+ },
+ exclude: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.exclude.help', {
+ defaultMessage: 'Specific bucket values to exclude from results',
+ }),
+ },
+ include: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.include.help', {
+ defaultMessage: 'Specific bucket values to include in results',
+ }),
+ },
+ json: {
+ types: ['string'],
+ help: i18n.translate('data.search.aggs.buckets.terms.json.help', {
+ defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch',
+ }),
+ },
+ },
+ fn: (input, args) => {
+ const { id, enabled, schema, ...rest } = args;
+
+ let json;
+ try {
+ json = args.json ? JSON.parse(args.json) : undefined;
+ } catch (e) {
+ throw new Error('Unable to parse json argument string');
+ }
+
+ // Need to spread this object to work around TS bug:
+ // https://github.com/microsoft/TypeScript/issues/15300#issuecomment-436793742
+ const orderAgg = args.orderAgg?.value ? { ...args.orderAgg.value } : undefined;
+
+ return {
+ type: 'agg_type',
+ value: {
+ id,
+ enabled,
+ schema,
+ type: aggName,
+ params: {
+ ...rest,
+ orderAgg,
+ json,
+ },
+ },
+ };
+ },
+});
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
index 3868d8f1bcd16..947394c97bdcd 100644
--- a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
+++ b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
@@ -36,14 +36,14 @@ const metricAggFilter = [
'!geo_centroid',
];
-const parentPipelineType = i18n.translate(
+export const parentPipelineType = i18n.translate(
'data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle',
{
defaultMessage: 'Parent Pipeline Aggregations',
}
);
-const parentPipelineAggHelper = {
+export const parentPipelineAggHelper = {
subtype: parentPipelineType,
params() {
return [
@@ -56,13 +56,9 @@ const parentPipelineAggHelper = {
name: 'customMetric',
type: 'agg',
allowedAggs: metricAggFilter,
- makeAgg(termsAgg, state: any) {
- state = state || { type: 'count' };
-
+ makeAgg(termsAgg, state = { type: 'count' }) {
const metricAgg = termsAgg.aggConfigs.createAggConfig(state, { addToAggConfigs: false });
-
metricAgg.id = termsAgg.id + '-metric';
-
return metricAgg;
},
modifyAggConfigOnSearchRequestStart: forwardModifyAggConfigOnSearchRequestStart(
@@ -89,5 +85,3 @@ const parentPipelineAggHelper = {
return subAgg ? subAgg.type.getFormat(subAgg) : new (FieldFormat.from(identity))();
},
};
-
-export { parentPipelineAggHelper, parentPipelineType };
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
index c1d05a39285b7..cee7841a8c3b9 100644
--- a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
+++ b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
@@ -43,14 +43,14 @@ const metricAggFilter: string[] = [
];
const bucketAggFilter: string[] = [];
-const siblingPipelineType = i18n.translate(
+export const siblingPipelineType = i18n.translate(
'data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle',
{
defaultMessage: 'Sibling pipeline aggregations',
}
);
-const siblingPipelineAggHelper = {
+export const siblingPipelineAggHelper = {
subtype: siblingPipelineType,
params() {
return [
@@ -59,11 +59,9 @@ const siblingPipelineAggHelper = {
type: 'agg',
allowedAggs: bucketAggFilter,
default: null,
- makeAgg(agg: IMetricAggConfig, state: any) {
- state = state || { type: 'date_histogram' };
+ makeAgg(agg: IMetricAggConfig, state = { type: 'date_histogram' }) {
const orderAgg = agg.aggConfigs.createAggConfig(state, { addToAggConfigs: false });
orderAgg.id = agg.id + '-bucket';
-
return orderAgg;
},
modifyAggConfigOnSearchRequestStart: forwardModifyAggConfigOnSearchRequestStart(
@@ -76,11 +74,9 @@ const siblingPipelineAggHelper = {
type: 'agg',
allowedAggs: metricAggFilter,
default: null,
- makeAgg(agg: IMetricAggConfig, state: any) {
- state = state || { type: 'count' };
+ makeAgg(agg: IMetricAggConfig, state = { type: 'count' }) {
const orderAgg = agg.aggConfigs.createAggConfig(state, { addToAggConfigs: false });
orderAgg.id = agg.id + '-metric';
-
return orderAgg;
},
modifyAggConfigOnSearchRequestStart: forwardModifyAggConfigOnSearchRequestStart(
@@ -98,5 +94,3 @@ const siblingPipelineAggHelper = {
: new (FieldFormat.from(identity))();
},
};
-
-export { siblingPipelineAggHelper, siblingPipelineType };
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts
index d20530a17ca65..7491f15aa3002 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts
+++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts
@@ -25,21 +25,28 @@ import {
import { AggConfigs, IAggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { METRIC_TYPES } from './metric_agg_types';
+import { FieldFormatsStart } from '../../../field_formats';
import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
import { notificationServiceMock } from '../../../../../../../src/core/public/mocks';
import { InternalStartServices } from '../../../types';
describe('AggTypesMetricsPercentileRanksProvider class', function() {
let aggConfigs: IAggConfigs;
- const aggTypesDependencies: PercentileRanksMetricAggDependencies = {
- getInternalStartServices: () =>
- (({
- fieldFormats: fieldFormatsServiceMock.createStartContract(),
- notifications: notificationServiceMock.createStartContract(),
- } as unknown) as InternalStartServices),
- };
+ let fieldFormats: FieldFormatsStart;
+ let aggTypesDependencies: PercentileRanksMetricAggDependencies;
beforeEach(() => {
+ fieldFormats = fieldFormatsServiceMock.createStartContract();
+ fieldFormats.getDefaultInstance = (() => ({
+ convert: (t?: string) => t,
+ })) as any;
+ aggTypesDependencies = {
+ getInternalStartServices: () =>
+ (({
+ fieldFormats,
+ notifications: notificationServiceMock.createStartContract(),
+ } as unknown) as InternalStartServices),
+ };
const typesRegistry = mockAggTypesRegistry([getPercentileRanksMetricAgg(aggTypesDependencies)]);
const field = {
name: 'bytes',
@@ -61,12 +68,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() {
type: METRIC_TYPES.PERCENTILE_RANKS,
schema: 'metric',
params: {
- field: {
- displayName: 'bytes',
- format: {
- convert: jest.fn(x => x),
- },
- },
+ field: 'bytes',
customLabel: 'my custom field label',
values: [5000, 10000],
},
diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts
index 0ac1e8417514c..76382c01bcc10 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts
+++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts
@@ -61,12 +61,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => {
type: METRIC_TYPES.PERCENTILES,
schema: 'metric',
params: {
- field: {
- displayName: 'bytes',
- format: {
- convert: jest.fn(x => `${x}th`),
- },
- },
+ field: 'bytes',
customLabel: 'prince',
percents: [95],
},
diff --git a/src/plugins/data/public/search/aggs/param_types/agg.ts b/src/plugins/data/public/search/aggs/param_types/agg.ts
index e5b53020c3159..e3f8c7c922170 100644
--- a/src/plugins/data/public/search/aggs/param_types/agg.ts
+++ b/src/plugins/data/public/search/aggs/param_types/agg.ts
@@ -17,13 +17,13 @@
* under the License.
*/
-import { AggConfig, IAggConfig } from '../agg_config';
+import { AggConfig, IAggConfig, AggConfigSerialized } from '../agg_config';
import { BaseParamType } from './base';
export class AggParamType extends BaseParamType<
TAggConfig
> {
- makeAgg: (agg: TAggConfig, state?: any) => TAggConfig;
+ makeAgg: (agg: TAggConfig, state?: AggConfigSerialized) => TAggConfig;
allowedAggs: string[] = [];
constructor(config: Record) {
@@ -42,17 +42,25 @@ export class AggParamType extends Ba
}
if (!config.serialize) {
this.serialize = (agg: TAggConfig) => {
- return agg.toJSON();
+ return agg.serialize();
};
}
if (!config.deserialize) {
- this.deserialize = (state: unknown, agg?: TAggConfig): TAggConfig => {
+ this.deserialize = (state: AggConfigSerialized, agg?: TAggConfig): TAggConfig => {
if (!agg) {
throw new Error('aggConfig was not provided to AggParamType deserialize function');
}
return this.makeAgg(agg, state);
};
}
+ if (!config.toExpressionAst) {
+ this.toExpressionAst = (agg: TAggConfig) => {
+ if (!agg || !agg.toExpressionAst) {
+ throw new Error('aggConfig was not provided to AggParamType toExpressionAst function');
+ }
+ return agg.toExpressionAst();
+ };
+ }
this.makeAgg = config.makeAgg;
this.valueType = AggConfig;
diff --git a/src/plugins/data/public/search/aggs/param_types/base.ts b/src/plugins/data/public/search/aggs/param_types/base.ts
index 2cbc5866e284d..a6f7e5adea043 100644
--- a/src/plugins/data/public/search/aggs/param_types/base.ts
+++ b/src/plugins/data/public/search/aggs/param_types/base.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { ExpressionAstFunction } from 'src/plugins/expressions/public';
import { IAggConfigs } from '../agg_configs';
import { IAggConfig } from '../agg_config';
import { FetchOptions } from '../../fetch';
@@ -37,6 +38,7 @@ export class BaseParamType {
) => void;
serialize: (value: any, aggConfig?: TAggConfig) => any;
deserialize: (value: any, aggConfig?: TAggConfig) => any;
+ toExpressionAst?: (value: any) => ExpressionAstFunction | undefined;
options: any[];
valueType?: any;
@@ -77,6 +79,7 @@ export class BaseParamType {
this.write = config.write || defaultWrite;
this.serialize = config.serialize;
this.deserialize = config.deserialize;
+ this.toExpressionAst = config.toExpressionAst;
this.options = config.options;
this.modifyAggConfigOnSearchRequestStart =
config.modifyAggConfigOnSearchRequestStart || function() {};
diff --git a/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts b/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts
new file mode 100644
index 0000000000000..cb0e37c0296d7
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/test_helpers/function_wrapper.ts
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { mapValues } from 'lodash';
+import {
+ AnyExpressionFunctionDefinition,
+ ExpressionFunctionDefinition,
+ ExecutionContext,
+} from '../../../../../../plugins/expressions/public';
+
+/**
+ * Takes a function spec and passes in default args,
+ * overriding with any provided args.
+ *
+ * Similar to the test helper used in Expressions & Canvas,
+ * however in this case we are ignoring the input & execution
+ * context, as they are not applicable to the agg type
+ * expression functions.
+ */
+export const functionWrapper = (spec: T) => {
+ const defaultArgs = mapValues(spec.args, argSpec => argSpec.default);
+ return (
+ args: T extends ExpressionFunctionDefinition<
+ infer Name,
+ infer Input,
+ infer Arguments,
+ infer Output,
+ infer Context
+ >
+ ? Arguments
+ : never
+ ) => spec.fn(null, { ...defaultArgs, ...args }, {} as ExecutionContext);
+};
diff --git a/src/plugins/data/public/search/aggs/test_helpers/index.ts b/src/plugins/data/public/search/aggs/test_helpers/index.ts
index 131f921586144..63f8ae0ce5f58 100644
--- a/src/plugins/data/public/search/aggs/test_helpers/index.ts
+++ b/src/plugins/data/public/search/aggs/test_helpers/index.ts
@@ -17,5 +17,6 @@
* under the License.
*/
+export { functionWrapper } from './function_wrapper';
export { mockAggTypesRegistry } from './mock_agg_types_registry';
export { mockDataServices } from './mock_data_services';
diff --git a/src/plugins/data/public/search/aggs/types.ts b/src/plugins/data/public/search/aggs/types.ts
index 4b2b1620ad1d3..95a7a45013567 100644
--- a/src/plugins/data/public/search/aggs/types.ts
+++ b/src/plugins/data/public/search/aggs/types.ts
@@ -19,21 +19,23 @@
import { IndexPattern } from '../../index_patterns';
import {
+ AggConfig,
+ AggConfigSerialized,
+ AggConfigs,
+ AggParamsTerms,
AggType,
+ aggTypeFieldFilters,
AggTypesRegistrySetup,
AggTypesRegistryStart,
- AggConfig,
- AggConfigs,
CreateAggConfigParams,
FieldParamType,
getCalculateAutoTimeExpression,
MetricAggType,
- aggTypeFieldFilters,
parentPipelineAggHelper,
siblingPipelineAggHelper,
} from './';
-export { IAggConfig } from './agg_config';
+export { IAggConfig, AggConfigSerialized } from './agg_config';
export { CreateAggConfigParams, IAggConfigs } from './agg_configs';
export { IAggType } from './agg_type';
export { AggParam, AggParamOption } from './agg_params';
@@ -70,3 +72,25 @@ export interface SearchAggsStart {
) => InstanceType;
types: AggTypesRegistryStart;
}
+
+/** @internal */
+export interface AggExpressionType {
+ type: 'agg_type';
+ value: AggConfigSerialized;
+}
+
+/** @internal */
+export type AggExpressionFunctionArgs<
+ Name extends keyof AggParamsMapping
+> = AggParamsMapping[Name] & Pick;
+
+/**
+ * A global list of the param interfaces for each agg type.
+ * For now this is internal, but eventually we will probably
+ * want to make it public.
+ *
+ * @internal
+ */
+export interface AggParamsMapping {
+ terms: AggParamsTerms;
+}
diff --git a/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts b/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
index 4ca976d328c91..78b4935077d10 100644
--- a/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
+++ b/src/plugins/data/public/search/expressions/utils/serialize_agg_config.ts
@@ -27,7 +27,7 @@ export const serializeAggConfig = (aggConfig: IAggConfig): KibanaDatatableColumn
return {
type: aggConfig.type.name,
indexPatternId: aggConfig.getIndexPattern().id,
- aggConfigParams: aggConfig.toJSON().params,
+ aggConfigParams: aggConfig.serialize().params,
};
};
diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts
index 19308dd387d3a..b1f7925bec4bb 100644
--- a/src/plugins/data/public/search/search_service.test.ts
+++ b/src/plugins/data/public/search/search_service.test.ts
@@ -18,9 +18,10 @@
*/
import { coreMock } from '../../../../core/public/mocks';
+import { CoreSetup } from '../../../../core/public';
+import { expressionsPluginMock } from '../../../../plugins/expressions/public/mocks';
import { SearchService } from './search_service';
-import { CoreSetup } from '../../../../core/public';
describe('Search service', () => {
let searchService: SearchService;
@@ -35,6 +36,7 @@ describe('Search service', () => {
it('exposes proper contract', async () => {
const setup = searchService.setup(mockCoreSetup, {
packageInfo: { version: '8' },
+ expressions: expressionsPluginMock.createSetupContract(),
} as any);
expect(setup).toHaveProperty('registerSearchStrategyProvider');
});
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 3f3266b5fe90f..b59524baa9fa7 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -18,6 +18,7 @@
*/
import { Plugin, CoreSetup, CoreStart, PackageInfo } from '../../../../core/public';
+import { ExpressionsSetup } from '../../../../plugins/expressions/public';
import { SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider } from './sync_search_strategy';
import {
@@ -37,6 +38,7 @@ import { GetInternalStartServicesFn } from '../types';
import { SearchInterceptor } from './search_interceptor';
import {
getAggTypes,
+ getAggTypesFunctions,
AggType,
AggTypesRegistry,
AggConfig,
@@ -52,9 +54,10 @@ import { FieldFormatsStart } from '../field_formats';
import { ISearchGeneric } from './i_search';
interface SearchServiceSetupDependencies {
+ expressions: ExpressionsSetup;
+ getInternalStartServices: GetInternalStartServicesFn;
packageInfo: PackageInfo;
query: QuerySetup;
- getInternalStartServices: GetInternalStartServicesFn;
}
interface SearchServiceStartDependencies {
@@ -97,22 +100,27 @@ export class SearchService implements Plugin {
public setup(
core: CoreSetup,
- { packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies
+ { expressions, packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies
): ISearchSetup {
this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo);
this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider);
this.registerSearchStrategyProvider(ES_SEARCH_STRATEGY, esSearchStrategyProvider);
const aggTypesSetup = this.aggTypesRegistry.setup();
+
+ // register each agg type
const aggTypes = getAggTypes({
query,
uiSettings: core.uiSettings,
getInternalStartServices,
});
-
aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b));
aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m));
+ // register expression functions for each agg type
+ const aggFunctions = getAggTypesFunctions();
+ aggFunctions.forEach(fn => expressions.registerFunction(fn));
+
return {
aggs: {
calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings),