parent
296f81cf05
commit
def8204814
|
@ -1597,7 +1597,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 3160
|
||||
"lineNumber": 174
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -1816,7 +1816,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 558
|
||||
"lineNumber": 56
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -1837,7 +1837,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 559
|
||||
"lineNumber": 57
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1848,7 +1848,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 562
|
||||
"lineNumber": 60
|
||||
},
|
||||
"signature": [
|
||||
"[number, number[]][]"
|
||||
|
@ -1859,7 +1859,7 @@
|
|||
"label": "[ColorSchemas.Greens]",
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 557
|
||||
"lineNumber": 55
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1875,7 +1875,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1078
|
||||
"lineNumber": 74
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -1896,7 +1896,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1079
|
||||
"lineNumber": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1907,7 +1907,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1082
|
||||
"lineNumber": 78
|
||||
},
|
||||
"signature": [
|
||||
"[number, number[]][]"
|
||||
|
@ -1918,7 +1918,7 @@
|
|||
"label": "[ColorSchemas.Greys]",
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1077
|
||||
"lineNumber": 73
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1934,7 +1934,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1598
|
||||
"lineNumber": 92
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -1955,7 +1955,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1599
|
||||
"lineNumber": 93
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1966,7 +1966,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1602
|
||||
"lineNumber": 96
|
||||
},
|
||||
"signature": [
|
||||
"[number, number[]][]"
|
||||
|
@ -1977,7 +1977,7 @@
|
|||
"label": "[ColorSchemas.Reds]",
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 1597
|
||||
"lineNumber": 91
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1993,7 +1993,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2118
|
||||
"lineNumber": 110
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -2014,7 +2014,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2119
|
||||
"lineNumber": 111
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -2025,7 +2025,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2122
|
||||
"lineNumber": 114
|
||||
},
|
||||
"signature": [
|
||||
"[number, number[]][]"
|
||||
|
@ -2036,7 +2036,7 @@
|
|||
"label": "[ColorSchemas.YellowToRed]",
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2117
|
||||
"lineNumber": 109
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -2052,7 +2052,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2639
|
||||
"lineNumber": 129
|
||||
},
|
||||
"signature": [
|
||||
{
|
||||
|
@ -2073,7 +2073,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2640
|
||||
"lineNumber": 130
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -2084,7 +2084,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2643
|
||||
"lineNumber": 133
|
||||
},
|
||||
"signature": [
|
||||
"[number, number[]][]"
|
||||
|
@ -2095,7 +2095,7 @@
|
|||
"label": "[ColorSchemas.GreenToRed]",
|
||||
"source": {
|
||||
"path": "src/plugins/charts/public/static/color_maps/color_maps.ts",
|
||||
"lineNumber": 2638
|
||||
"lineNumber": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
1394
api_docs/data.json
1394
api_docs/data.json
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -21952,6 +21952,41 @@
|
|||
"returnComment": [],
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"id": "def-common.createMockContext",
|
||||
"type": "Function",
|
||||
"children": [],
|
||||
"signature": [
|
||||
"() => ",
|
||||
{
|
||||
"pluginId": "expressions",
|
||||
"scope": "common",
|
||||
"docId": "kibExpressionsPluginApi",
|
||||
"section": "def-common.ExecutionContext",
|
||||
"text": "ExecutionContext"
|
||||
},
|
||||
"<",
|
||||
{
|
||||
"pluginId": "inspector",
|
||||
"scope": "common",
|
||||
"docId": "kibInspectorPluginApi",
|
||||
"section": "def-common.Adapters",
|
||||
"text": "Adapters"
|
||||
},
|
||||
", ",
|
||||
"SerializableState",
|
||||
">"
|
||||
],
|
||||
"description": [],
|
||||
"label": "createMockContext",
|
||||
"source": {
|
||||
"path": "src/plugins/expressions/common/util/test_utils.ts",
|
||||
"lineNumber": 11
|
||||
},
|
||||
"tags": [],
|
||||
"returnComment": [],
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"id": "def-common.format",
|
||||
"type": "Function",
|
||||
|
@ -22465,6 +22500,52 @@
|
|||
"tags": [],
|
||||
"returnComment": [],
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"id": "def-common.unboxExpressionValue",
|
||||
"type": "Function",
|
||||
"label": "unboxExpressionValue",
|
||||
"signature": [
|
||||
"({\n type,\n ...value\n}: ",
|
||||
{
|
||||
"pluginId": "expressions",
|
||||
"scope": "common",
|
||||
"docId": "kibExpressionsPluginApi",
|
||||
"section": "def-common.ExpressionValueBoxed",
|
||||
"text": "ExpressionValueBoxed"
|
||||
},
|
||||
"<string, T>) => T"
|
||||
],
|
||||
"description": [],
|
||||
"children": [
|
||||
{
|
||||
"type": "CompoundType",
|
||||
"label": "{\n type,\n ...value\n}",
|
||||
"isRequired": true,
|
||||
"signature": [
|
||||
{
|
||||
"pluginId": "expressions",
|
||||
"scope": "common",
|
||||
"docId": "kibExpressionsPluginApi",
|
||||
"section": "def-common.ExpressionValueBoxed",
|
||||
"text": "ExpressionValueBoxed"
|
||||
},
|
||||
"<string, T>"
|
||||
],
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/expressions/common/expression_types/unbox_expression_value.ts",
|
||||
"lineNumber": 11
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [],
|
||||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "src/plugins/expressions/common/expression_types/unbox_expression_value.ts",
|
||||
"lineNumber": 11
|
||||
},
|
||||
"initialIsOpen": false
|
||||
}
|
||||
],
|
||||
"interfaces": [
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -288,7 +288,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts",
|
||||
"lineNumber": 44
|
||||
"lineNumber": 46
|
||||
},
|
||||
"signature": [
|
||||
"\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\" | undefined"
|
||||
|
@ -302,7 +302,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts",
|
||||
"lineNumber": 45
|
||||
"lineNumber": 47
|
||||
},
|
||||
"signature": [
|
||||
"string | undefined"
|
||||
|
@ -311,7 +311,7 @@
|
|||
],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts",
|
||||
"lineNumber": 43
|
||||
"lineNumber": 45
|
||||
},
|
||||
"initialIsOpen": false
|
||||
},
|
||||
|
@ -1318,7 +1318,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx",
|
||||
"lineNumber": 125
|
||||
"lineNumber": 127
|
||||
},
|
||||
"signature": [
|
||||
"BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"avg\"; }"
|
||||
|
@ -1475,7 +1475,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx",
|
||||
"lineNumber": 127
|
||||
"lineNumber": 129
|
||||
},
|
||||
"signature": [
|
||||
"BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"max\"; }"
|
||||
|
@ -1490,7 +1490,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx",
|
||||
"lineNumber": 128
|
||||
"lineNumber": 130
|
||||
},
|
||||
"signature": [
|
||||
"BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"median\"; }"
|
||||
|
@ -1505,7 +1505,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx",
|
||||
"lineNumber": 126
|
||||
"lineNumber": 128
|
||||
},
|
||||
"signature": [
|
||||
"BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"min\"; }"
|
||||
|
@ -1538,7 +1538,7 @@
|
|||
],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts",
|
||||
"lineNumber": 405
|
||||
"lineNumber": 406
|
||||
},
|
||||
"signature": [
|
||||
"\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\""
|
||||
|
@ -1598,7 +1598,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx",
|
||||
"lineNumber": 124
|
||||
"lineNumber": 126
|
||||
},
|
||||
"signature": [
|
||||
"BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"sum\"; }"
|
||||
|
|
|
@ -2312,7 +2312,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/observability/server/plugin.ts",
|
||||
"lineNumber": 22
|
||||
"lineNumber": 23
|
||||
},
|
||||
"signature": [
|
||||
"LazyScopedAnnotationsClientFactory"
|
||||
|
@ -2321,7 +2321,7 @@
|
|||
],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/observability/server/plugin.ts",
|
||||
"lineNumber": 21
|
||||
"lineNumber": 22
|
||||
},
|
||||
"lifecycle": "setup",
|
||||
"initialIsOpen": true
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [AggFunctionsMapping](./kibana-plugin-plugins-data-public.aggfunctionsmapping.md) > [aggFilteredMetric](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md)
|
||||
|
||||
## AggFunctionsMapping.aggFilteredMetric property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
aggFilteredMetric: ReturnType<typeof aggFilteredMetric>;
|
||||
```
|
|
@ -28,6 +28,7 @@ export interface AggFunctionsMapping
|
|||
| [aggDateRange](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggdaterange.md) | <code>ReturnType<typeof aggDateRange></code> | |
|
||||
| [aggDerivative](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggderivative.md) | <code>ReturnType<typeof aggDerivative></code> | |
|
||||
| [aggFilter](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilter.md) | <code>ReturnType<typeof aggFilter></code> | |
|
||||
| [aggFilteredMetric](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilteredmetric.md) | <code>ReturnType<typeof aggFilteredMetric></code> | |
|
||||
| [aggFilters](./kibana-plugin-plugins-data-public.aggfunctionsmapping.aggfilters.md) | <code>ReturnType<typeof aggFilters></code> | |
|
||||
| [aggGeoBounds](./kibana-plugin-plugins-data-public.aggfunctionsmapping.agggeobounds.md) | <code>ReturnType<typeof aggGeoBounds></code> | |
|
||||
| [aggGeoCentroid](./kibana-plugin-plugins-data-public.aggfunctionsmapping.agggeocentroid.md) | <code>ReturnType<typeof aggGeoCentroid></code> | |
|
||||
|
|
|
@ -20,6 +20,7 @@ export declare enum METRIC_TYPES
|
|||
| COUNT | <code>"count"</code> | |
|
||||
| CUMULATIVE\_SUM | <code>"cumulative_sum"</code> | |
|
||||
| DERIVATIVE | <code>"derivative"</code> | |
|
||||
| FILTERED\_METRIC | <code>"filtered_metric"</code> | |
|
||||
| GEO\_BOUNDS | <code>"geo_bounds"</code> | |
|
||||
| GEO\_CENTROID | <code>"geo_centroid"</code> | |
|
||||
| MAX | <code>"max"</code> | |
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) > [aggFilteredMetric](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md)
|
||||
|
||||
## AggFunctionsMapping.aggFilteredMetric property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
aggFilteredMetric: ReturnType<typeof aggFilteredMetric>;
|
||||
```
|
|
@ -28,6 +28,7 @@ export interface AggFunctionsMapping
|
|||
| [aggDateRange](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggdaterange.md) | <code>ReturnType<typeof aggDateRange></code> | |
|
||||
| [aggDerivative](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggderivative.md) | <code>ReturnType<typeof aggDerivative></code> | |
|
||||
| [aggFilter](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilter.md) | <code>ReturnType<typeof aggFilter></code> | |
|
||||
| [aggFilteredMetric](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilteredmetric.md) | <code>ReturnType<typeof aggFilteredMetric></code> | |
|
||||
| [aggFilters](./kibana-plugin-plugins-data-server.aggfunctionsmapping.aggfilters.md) | <code>ReturnType<typeof aggFilters></code> | |
|
||||
| [aggGeoBounds](./kibana-plugin-plugins-data-server.aggfunctionsmapping.agggeobounds.md) | <code>ReturnType<typeof aggGeoBounds></code> | |
|
||||
| [aggGeoCentroid](./kibana-plugin-plugins-data-server.aggfunctionsmapping.agggeocentroid.md) | <code>ReturnType<typeof aggGeoCentroid></code> | |
|
||||
|
|
|
@ -20,6 +20,7 @@ export declare enum METRIC_TYPES
|
|||
| COUNT | <code>"count"</code> | |
|
||||
| CUMULATIVE\_SUM | <code>"cumulative_sum"</code> | |
|
||||
| DERIVATIVE | <code>"derivative"</code> | |
|
||||
| FILTERED\_METRIC | <code>"filtered_metric"</code> | |
|
||||
| GEO\_BOUNDS | <code>"geo_bounds"</code> | |
|
||||
| GEO\_CENTROID | <code>"geo_centroid"</code> | |
|
||||
| MAX | <code>"max"</code> | |
|
||||
|
|
|
@ -232,7 +232,9 @@ export class AggConfig {
|
|||
const output = this.write(aggConfigs) as any;
|
||||
|
||||
const configDsl = {} as any;
|
||||
configDsl[this.type.dslName || this.type.name] = output.params;
|
||||
if (!this.type.hasNoDslParams) {
|
||||
configDsl[this.type.dslName || this.type.name] = output.params;
|
||||
}
|
||||
|
||||
// if the config requires subAggs, write them to the dsl as well
|
||||
if (this.subAggs.length) {
|
||||
|
|
|
@ -21,7 +21,14 @@ import { TimeRange } from '../../../common';
|
|||
function removeParentAggs(obj: any) {
|
||||
for (const prop in obj) {
|
||||
if (prop === 'parentAggs') delete obj[prop];
|
||||
else if (typeof obj[prop] === 'object') removeParentAggs(obj[prop]);
|
||||
else if (typeof obj[prop] === 'object') {
|
||||
const hasParentAggsKey = 'parentAggs' in obj[prop];
|
||||
removeParentAggs(obj[prop]);
|
||||
// delete object if parentAggs was the last key
|
||||
if (hasParentAggsKey && Object.keys(obj[prop]).length === 0) {
|
||||
delete obj[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,10 +200,12 @@ export class AggConfigs {
|
|||
// advance the cursor and nest under the previous agg, or
|
||||
// put it on the same level if the previous agg doesn't accept
|
||||
// sub aggs
|
||||
dslLvlCursor = prevDsl.aggs || dslLvlCursor;
|
||||
dslLvlCursor = prevDsl?.aggs || dslLvlCursor;
|
||||
}
|
||||
|
||||
const dsl = (dslLvlCursor[config.id] = config.toDsl(this));
|
||||
const dsl = config.type.hasNoDslParams
|
||||
? config.toDsl(this)
|
||||
: (dslLvlCursor[config.id] = config.toDsl(this));
|
||||
let subAggs: any;
|
||||
|
||||
parseParentAggs(dslLvlCursor, dsl);
|
||||
|
@ -206,6 +215,11 @@ export class AggConfigs {
|
|||
subAggs = dsl.aggs || (dsl.aggs = {});
|
||||
}
|
||||
|
||||
if (subAggs) {
|
||||
_.each(subAggs, (agg) => {
|
||||
parseParentAggs(subAggs, agg);
|
||||
});
|
||||
}
|
||||
if (subAggs && nestedMetrics) {
|
||||
nestedMetrics.forEach((agg: any) => {
|
||||
subAggs[agg.config.id] = agg.dsl;
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface AggTypeConfig<
|
|||
makeLabel?: ((aggConfig: TAggConfig) => string) | (() => string);
|
||||
ordered?: any;
|
||||
hasNoDsl?: boolean;
|
||||
hasNoDslParams?: boolean;
|
||||
params?: Array<Partial<TParam>>;
|
||||
valueType?: DatatableColumnType;
|
||||
getRequestAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void);
|
||||
|
@ -129,6 +130,12 @@ export class AggType<
|
|||
* @type {Boolean}
|
||||
*/
|
||||
hasNoDsl: boolean;
|
||||
/**
|
||||
* Flag that prevents params from this aggregation from being included in the dsl. Sibling and parent aggs are still written.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
hasNoDslParams: boolean;
|
||||
/**
|
||||
* The method to create a filter representation of the bucket
|
||||
* @param {object} aggConfig The instance of the aggConfig
|
||||
|
@ -232,6 +239,7 @@ export class AggType<
|
|||
this.makeLabel = config.makeLabel || constant(this.name);
|
||||
this.ordered = config.ordered;
|
||||
this.hasNoDsl = !!config.hasNoDsl;
|
||||
this.hasNoDslParams = !!config.hasNoDslParams;
|
||||
|
||||
if (config.createFilter) {
|
||||
this.createFilter = config.createFilter;
|
||||
|
|
|
@ -44,6 +44,7 @@ export const getAggTypes = () => ({
|
|||
{ name: METRIC_TYPES.SUM_BUCKET, fn: metrics.getBucketSumMetricAgg },
|
||||
{ name: METRIC_TYPES.MIN_BUCKET, fn: metrics.getBucketMinMetricAgg },
|
||||
{ name: METRIC_TYPES.MAX_BUCKET, fn: metrics.getBucketMaxMetricAgg },
|
||||
{ name: METRIC_TYPES.FILTERED_METRIC, fn: metrics.getFilteredMetricAgg },
|
||||
{ name: METRIC_TYPES.GEO_BOUNDS, fn: metrics.getGeoBoundsMetricAgg },
|
||||
{ name: METRIC_TYPES.GEO_CENTROID, fn: metrics.getGeoCentroidMetricAgg },
|
||||
],
|
||||
|
@ -80,6 +81,7 @@ export const getAggTypesFunctions = () => [
|
|||
metrics.aggBucketMax,
|
||||
metrics.aggBucketMin,
|
||||
metrics.aggBucketSum,
|
||||
metrics.aggFilteredMetric,
|
||||
metrics.aggCardinality,
|
||||
metrics.aggCount,
|
||||
metrics.aggCumulativeSum,
|
||||
|
|
|
@ -97,6 +97,7 @@ describe('Aggs service', () => {
|
|||
"sum_bucket",
|
||||
"min_bucket",
|
||||
"max_bucket",
|
||||
"filtered_metric",
|
||||
"geo_bounds",
|
||||
"geo_centroid",
|
||||
]
|
||||
|
@ -142,6 +143,7 @@ describe('Aggs service', () => {
|
|||
"sum_bucket",
|
||||
"min_bucket",
|
||||
"max_bucket",
|
||||
"filtered_metric",
|
||||
"geo_bounds",
|
||||
"geo_centroid",
|
||||
]
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BucketAggType } from './bucket_agg_type';
|
||||
import { BUCKET_TYPES } from './bucket_agg_types';
|
||||
import { GeoBoundingBox } from './lib/geo_point';
|
||||
import { aggFilterFnName } from './filter_fn';
|
||||
import { BaseAggParams } from '../types';
|
||||
import { Query } from '../../../types';
|
||||
import { buildEsQuery, getEsQueryConfig } from '../../../es_query';
|
||||
|
||||
const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', {
|
||||
defaultMessage: 'Filter',
|
||||
|
@ -21,7 +24,7 @@ export interface AggParamsFilter extends BaseAggParams {
|
|||
geo_bounding_box?: GeoBoundingBox;
|
||||
}
|
||||
|
||||
export const getFilterBucketAgg = () =>
|
||||
export const getFilterBucketAgg = ({ getConfig }: { getConfig: <T = any>(key: string) => any }) =>
|
||||
new BucketAggType({
|
||||
name: BUCKET_TYPES.FILTER,
|
||||
expressionName: aggFilterFnName,
|
||||
|
@ -31,5 +34,27 @@ export const getFilterBucketAgg = () =>
|
|||
{
|
||||
name: 'geo_bounding_box',
|
||||
},
|
||||
{
|
||||
name: 'filter',
|
||||
write(aggConfig, output) {
|
||||
const filter: Query = aggConfig.params.filter;
|
||||
|
||||
const input = cloneDeep(filter);
|
||||
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
const esQueryConfigs = getEsQueryConfig({ get: getConfig });
|
||||
const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs);
|
||||
|
||||
if (!query) {
|
||||
console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console
|
||||
return;
|
||||
}
|
||||
|
||||
output.params = query;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('agg_expression_functions', () => {
|
|||
"id": undefined,
|
||||
"params": Object {
|
||||
"customLabel": undefined,
|
||||
"filter": undefined,
|
||||
"geo_bounding_box": undefined,
|
||||
"json": undefined,
|
||||
},
|
||||
|
@ -46,6 +47,7 @@ describe('agg_expression_functions', () => {
|
|||
"id": undefined,
|
||||
"params": Object {
|
||||
"customLabel": undefined,
|
||||
"filter": undefined,
|
||||
"geo_bounding_box": Object {
|
||||
"wkt": "BBOX (-74.1, -71.12, 40.73, 40.01)",
|
||||
},
|
||||
|
@ -57,6 +59,25 @@ describe('agg_expression_functions', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('correctly parses filter string argument', () => {
|
||||
const actual = fn({
|
||||
filter: '{ "language": "kuery", "query": "a: b" }',
|
||||
});
|
||||
|
||||
expect(actual.value.params.filter).toEqual({ language: 'kuery', query: 'a: b' });
|
||||
});
|
||||
|
||||
test('errors out if geo_bounding_box is used together with filter', () => {
|
||||
expect(() =>
|
||||
fn({
|
||||
filter: '{ "language": "kuery", "query": "a: b" }',
|
||||
geo_bounding_box: JSON.stringify({
|
||||
wkt: 'BBOX (-74.1, -71.12, 40.73, 40.01)',
|
||||
}),
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('correctly parses json string argument', () => {
|
||||
const actual = fn({
|
||||
json: '{ "foo": true }',
|
||||
|
|
|
@ -17,7 +17,7 @@ export const aggFilterFnName = 'aggFilter';
|
|||
type Input = any;
|
||||
type AggArgs = AggExpressionFunctionArgs<typeof BUCKET_TYPES.FILTER>;
|
||||
|
||||
type Arguments = Assign<AggArgs, { geo_bounding_box?: string }>;
|
||||
type Arguments = Assign<AggArgs, { geo_bounding_box?: string; filter?: string }>;
|
||||
|
||||
type Output = AggExpressionType;
|
||||
type FunctionDefinition = ExpressionFunctionDefinition<
|
||||
|
@ -59,6 +59,13 @@ export const aggFilter = (): FunctionDefinition => ({
|
|||
defaultMessage: 'Filter results based on a point location within a bounding box',
|
||||
}),
|
||||
},
|
||||
filter: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.buckets.filter.filter.help', {
|
||||
defaultMessage:
|
||||
'Filter results based on a kql or lucene query. Do not use together with geo_bounding_box',
|
||||
}),
|
||||
},
|
||||
json: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.buckets.filter.json.help', {
|
||||
|
@ -75,6 +82,13 @@ export const aggFilter = (): FunctionDefinition => ({
|
|||
fn: (input, args) => {
|
||||
const { id, enabled, schema, ...rest } = args;
|
||||
|
||||
const geoBoundingBox = getParsedValue(args, 'geo_bounding_box');
|
||||
const filter = getParsedValue(args, 'filter');
|
||||
|
||||
if (geoBoundingBox && filter) {
|
||||
throw new Error("filter and geo_bounding_box can't be used together");
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'agg_type',
|
||||
value: {
|
||||
|
@ -84,7 +98,8 @@ export const aggFilter = (): FunctionDefinition => ({
|
|||
type: BUCKET_TYPES.FILTER,
|
||||
params: {
|
||||
...rest,
|
||||
geo_bounding_box: getParsedValue(args, 'geo_bounding_box'),
|
||||
geo_bounding_box: geoBoundingBox,
|
||||
filter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { AggConfigs, IAggConfigs } from '../agg_configs';
|
||||
import { mockAggTypesRegistry } from '../test_helpers';
|
||||
import { METRIC_TYPES } from './metric_agg_types';
|
||||
|
||||
describe('filtered metric agg type', () => {
|
||||
let aggConfigs: IAggConfigs;
|
||||
|
||||
beforeEach(() => {
|
||||
const typesRegistry = mockAggTypesRegistry();
|
||||
const field = {
|
||||
name: 'bytes',
|
||||
};
|
||||
const indexPattern = {
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
fields: {
|
||||
getByName: () => field,
|
||||
filter: () => [field],
|
||||
},
|
||||
} as any;
|
||||
|
||||
aggConfigs = new AggConfigs(
|
||||
indexPattern,
|
||||
[
|
||||
{
|
||||
id: METRIC_TYPES.FILTERED_METRIC,
|
||||
type: METRIC_TYPES.FILTERED_METRIC,
|
||||
schema: 'metric',
|
||||
params: {
|
||||
customBucket: {
|
||||
type: 'filter',
|
||||
params: {
|
||||
filter: { language: 'kuery', query: 'a: b' },
|
||||
},
|
||||
},
|
||||
customMetric: {
|
||||
type: 'cardinality',
|
||||
params: {
|
||||
field: 'bytes',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
typesRegistry,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('converts the response', () => {
|
||||
const agg = aggConfigs.getResponseAggs()[0];
|
||||
|
||||
expect(
|
||||
agg.getValue({
|
||||
'filtered_metric-bucket': {
|
||||
'filtered_metric-metric': {
|
||||
value: 10,
|
||||
},
|
||||
},
|
||||
})
|
||||
).toEqual(10);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { MetricAggType } from './metric_agg_type';
|
||||
import { makeNestedLabel } from './lib/make_nested_label';
|
||||
import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper';
|
||||
import { METRIC_TYPES } from './metric_agg_types';
|
||||
import { AggConfigSerialized, BaseAggParams } from '../types';
|
||||
import { aggFilteredMetricFnName } from './filtered_metric_fn';
|
||||
|
||||
export interface AggParamsFilteredMetric extends BaseAggParams {
|
||||
customMetric?: AggConfigSerialized;
|
||||
customBucket?: AggConfigSerialized;
|
||||
}
|
||||
|
||||
const filteredMetricLabel = i18n.translate('data.search.aggs.metrics.filteredMetricLabel', {
|
||||
defaultMessage: 'filtered',
|
||||
});
|
||||
|
||||
const filteredMetricTitle = i18n.translate('data.search.aggs.metrics.filteredMetricTitle', {
|
||||
defaultMessage: 'Filtered metric',
|
||||
});
|
||||
|
||||
export const getFilteredMetricAgg = () => {
|
||||
const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper;
|
||||
|
||||
return new MetricAggType({
|
||||
name: METRIC_TYPES.FILTERED_METRIC,
|
||||
expressionName: aggFilteredMetricFnName,
|
||||
title: filteredMetricTitle,
|
||||
makeLabel: (agg) => makeNestedLabel(agg, filteredMetricLabel),
|
||||
subtype,
|
||||
params: [...params(['filter'])],
|
||||
hasNoDslParams: true,
|
||||
getSerializedFormat,
|
||||
getValue(agg, bucket) {
|
||||
const customMetric = agg.getParam('customMetric');
|
||||
const customBucket = agg.getParam('customBucket');
|
||||
return customMetric.getValue(bucket[customBucket.id]);
|
||||
},
|
||||
getValueBucketPath(agg) {
|
||||
const customBucket = agg.getParam('customBucket');
|
||||
const customMetric = agg.getParam('customMetric');
|
||||
if (customMetric.type.name === 'count') {
|
||||
return customBucket.getValueBucketPath();
|
||||
}
|
||||
return `${customBucket.getValueBucketPath()}>${customMetric.getValueBucketPath()}`;
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { functionWrapper } from '../test_helpers';
|
||||
import { aggFilteredMetric } from './filtered_metric_fn';
|
||||
|
||||
describe('agg_expression_functions', () => {
|
||||
describe('aggFilteredMetric', () => {
|
||||
const fn = functionWrapper(aggFilteredMetric());
|
||||
|
||||
test('handles customMetric and customBucket as a subexpression', () => {
|
||||
const actual = fn({
|
||||
customMetric: fn({}),
|
||||
customBucket: fn({}),
|
||||
});
|
||||
|
||||
expect(actual.value.params).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"customBucket": Object {
|
||||
"enabled": true,
|
||||
"id": undefined,
|
||||
"params": Object {
|
||||
"customBucket": undefined,
|
||||
"customLabel": undefined,
|
||||
"customMetric": undefined,
|
||||
},
|
||||
"schema": undefined,
|
||||
"type": "filtered_metric",
|
||||
},
|
||||
"customLabel": undefined,
|
||||
"customMetric": Object {
|
||||
"enabled": true,
|
||||
"id": undefined,
|
||||
"params": Object {
|
||||
"customBucket": undefined,
|
||||
"customLabel": undefined,
|
||||
"customMetric": undefined,
|
||||
},
|
||||
"schema": undefined,
|
||||
"type": "filtered_metric",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
|
||||
import { AggExpressionType, AggExpressionFunctionArgs, METRIC_TYPES } from '../';
|
||||
|
||||
export const aggFilteredMetricFnName = 'aggFilteredMetric';
|
||||
|
||||
type Input = any;
|
||||
type AggArgs = AggExpressionFunctionArgs<typeof METRIC_TYPES.FILTERED_METRIC>;
|
||||
type Arguments = Assign<
|
||||
AggArgs,
|
||||
{ customBucket?: AggExpressionType; customMetric?: AggExpressionType }
|
||||
>;
|
||||
type Output = AggExpressionType;
|
||||
type FunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof aggFilteredMetricFnName,
|
||||
Input,
|
||||
Arguments,
|
||||
Output
|
||||
>;
|
||||
|
||||
export const aggFilteredMetric = (): FunctionDefinition => ({
|
||||
name: aggFilteredMetricFnName,
|
||||
help: i18n.translate('data.search.aggs.function.metrics.filtered_metric.help', {
|
||||
defaultMessage: 'Generates a serialized agg config for a filtered metric agg',
|
||||
}),
|
||||
type: 'agg_type',
|
||||
args: {
|
||||
id: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.id.help', {
|
||||
defaultMessage: 'ID for this aggregation',
|
||||
}),
|
||||
},
|
||||
enabled: {
|
||||
types: ['boolean'],
|
||||
default: true,
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.enabled.help', {
|
||||
defaultMessage: 'Specifies whether this aggregation should be enabled',
|
||||
}),
|
||||
},
|
||||
schema: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.schema.help', {
|
||||
defaultMessage: 'Schema to use for this aggregation',
|
||||
}),
|
||||
},
|
||||
customBucket: {
|
||||
types: ['agg_type'],
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.customBucket.help', {
|
||||
defaultMessage:
|
||||
'Agg config to use for building sibling pipeline aggregations. Has to be a filter aggregation',
|
||||
}),
|
||||
},
|
||||
customMetric: {
|
||||
types: ['agg_type'],
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.customMetric.help', {
|
||||
defaultMessage: 'Agg config to use for building sibling pipeline aggregations',
|
||||
}),
|
||||
},
|
||||
customLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.filtered_metric.customLabel.help', {
|
||||
defaultMessage: 'Represents a custom label for this aggregation',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (input, args) => {
|
||||
const { id, enabled, schema, ...rest } = args;
|
||||
|
||||
return {
|
||||
type: 'agg_type',
|
||||
value: {
|
||||
id,
|
||||
enabled,
|
||||
schema,
|
||||
type: METRIC_TYPES.FILTERED_METRIC,
|
||||
params: {
|
||||
...rest,
|
||||
customBucket: args.customBucket?.value,
|
||||
customMetric: args.customMetric?.value,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -16,6 +16,8 @@ export * from './bucket_min_fn';
|
|||
export * from './bucket_min';
|
||||
export * from './bucket_sum_fn';
|
||||
export * from './bucket_sum';
|
||||
export * from './filtered_metric_fn';
|
||||
export * from './filtered_metric';
|
||||
export * from './cardinality_fn';
|
||||
export * from './cardinality';
|
||||
export * from './count';
|
||||
|
|
|
@ -21,6 +21,7 @@ const metricAggFilter = [
|
|||
'!std_dev',
|
||||
'!geo_bounds',
|
||||
'!geo_centroid',
|
||||
'!filtered_metric',
|
||||
];
|
||||
|
||||
export const parentPipelineType = i18n.translate(
|
||||
|
|
|
@ -27,6 +27,7 @@ const metricAggFilter: string[] = [
|
|||
'!cumulative_sum',
|
||||
'!geo_bounds',
|
||||
'!geo_centroid',
|
||||
'!filtered_metric',
|
||||
];
|
||||
const bucketAggFilter: string[] = [];
|
||||
|
||||
|
@ -39,12 +40,12 @@ export const siblingPipelineType = i18n.translate(
|
|||
|
||||
export const siblingPipelineAggHelper = {
|
||||
subtype: siblingPipelineType,
|
||||
params() {
|
||||
params(bucketFilter = bucketAggFilter) {
|
||||
return [
|
||||
{
|
||||
name: 'customBucket',
|
||||
type: 'agg',
|
||||
allowedAggs: bucketAggFilter,
|
||||
allowedAggs: bucketFilter,
|
||||
default: null,
|
||||
makeAgg(agg: IMetricAggConfig, state = { type: 'date_histogram' }) {
|
||||
const orderAgg = agg.aggConfigs.createAggConfig(state, { addToAggConfigs: false });
|
||||
|
@ -69,7 +70,8 @@ export const siblingPipelineAggHelper = {
|
|||
modifyAggConfigOnSearchRequestStart: forwardModifyAggConfigOnSearchRequestStart(
|
||||
'customMetric'
|
||||
),
|
||||
write: siblingPipelineAggWriter,
|
||||
write: (agg: IMetricAggConfig, output: Record<string, any>) =>
|
||||
siblingPipelineAggWriter(agg, output),
|
||||
},
|
||||
] as Array<MetricAggParam<IMetricAggConfig>>;
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
export enum METRIC_TYPES {
|
||||
AVG = 'avg',
|
||||
FILTERED_METRIC = 'filtered_metric',
|
||||
CARDINALITY = 'cardinality',
|
||||
AVG_BUCKET = 'avg_bucket',
|
||||
MAX_BUCKET = 'max_bucket',
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
AggParamsBucketMax,
|
||||
AggParamsBucketMin,
|
||||
AggParamsBucketSum,
|
||||
AggParamsFilteredMetric,
|
||||
AggParamsCardinality,
|
||||
AggParamsCumulativeSum,
|
||||
AggParamsDateHistogram,
|
||||
|
@ -84,6 +85,7 @@ import {
|
|||
getCalculateAutoTimeExpression,
|
||||
METRIC_TYPES,
|
||||
AggConfig,
|
||||
aggFilteredMetric,
|
||||
} from './';
|
||||
|
||||
export { IAggConfig, AggConfigSerialized } from './agg_config';
|
||||
|
@ -188,6 +190,7 @@ export interface AggParamsMapping {
|
|||
[METRIC_TYPES.MAX_BUCKET]: AggParamsBucketMax;
|
||||
[METRIC_TYPES.MIN_BUCKET]: AggParamsBucketMin;
|
||||
[METRIC_TYPES.SUM_BUCKET]: AggParamsBucketSum;
|
||||
[METRIC_TYPES.FILTERED_METRIC]: AggParamsFilteredMetric;
|
||||
[METRIC_TYPES.CUMULATIVE_SUM]: AggParamsCumulativeSum;
|
||||
[METRIC_TYPES.DERIVATIVE]: AggParamsDerivative;
|
||||
[METRIC_TYPES.MOVING_FN]: AggParamsMovingAvg;
|
||||
|
@ -217,6 +220,7 @@ export interface AggFunctionsMapping {
|
|||
aggBucketMax: ReturnType<typeof aggBucketMax>;
|
||||
aggBucketMin: ReturnType<typeof aggBucketMin>;
|
||||
aggBucketSum: ReturnType<typeof aggBucketSum>;
|
||||
aggFilteredMetric: ReturnType<typeof aggFilteredMetric>;
|
||||
aggCardinality: ReturnType<typeof aggCardinality>;
|
||||
aggCount: ReturnType<typeof aggCount>;
|
||||
aggCumulativeSum: ReturnType<typeof aggCumulativeSum>;
|
||||
|
|
|
@ -329,6 +329,10 @@ export interface AggFunctionsMapping {
|
|||
//
|
||||
// (undocumented)
|
||||
aggFilter: ReturnType<typeof aggFilter>;
|
||||
// Warning: (ae-forgotten-export) The symbol "aggFilteredMetric" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
aggFilteredMetric: ReturnType<typeof aggFilteredMetric>;
|
||||
// Warning: (ae-forgotten-export) The symbol "aggFilters" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
@ -711,7 +715,7 @@ export const ES_SEARCH_STRATEGY = "es";
|
|||
// Warning: (ae-missing-release-tag) "EsaggsExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_34, Arguments_20, Output_34>;
|
||||
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_35, Arguments_21, Output_35>;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts
|
||||
|
@ -720,7 +724,7 @@ export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'e
|
|||
// Warning: (ae-missing-release-tag) "EsdslExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2<typeof name_3, Input_35, Arguments_22, Output_35>;
|
||||
export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition_2<typeof name_3, Input_36, Arguments_23, Output_36>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
@ -861,7 +865,7 @@ export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', Ex
|
|||
// Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_21, Promise<KibanaContext>, ExecutionContext<Adapters_2, ExecutionContextSearch>>;
|
||||
export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_22, Promise<KibanaContext>, ExecutionContext<Adapters_2, ExecutionContextSearch>>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
@ -1838,6 +1842,8 @@ export enum METRIC_TYPES {
|
|||
// (undocumented)
|
||||
DERIVATIVE = "derivative",
|
||||
// (undocumented)
|
||||
FILTERED_METRIC = "filtered_metric",
|
||||
// (undocumented)
|
||||
GEO_BOUNDS = "geo_bounds",
|
||||
// (undocumented)
|
||||
GEO_CENTROID = "geo_centroid",
|
||||
|
@ -2657,7 +2663,7 @@ export const UI_SETTINGS: {
|
|||
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:65:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/common/search/aggs/types.ts:141:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:56:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('AggsService - public', () => {
|
|||
service.setup(setupDeps);
|
||||
const start = service.start(startDeps);
|
||||
expect(start.types.getAll().buckets.length).toBe(11);
|
||||
expect(start.types.getAll().metrics.length).toBe(21);
|
||||
expect(start.types.getAll().metrics.length).toBe(22);
|
||||
});
|
||||
|
||||
test('registers custom agg types', () => {
|
||||
|
@ -71,7 +71,7 @@ describe('AggsService - public', () => {
|
|||
const start = service.start(startDeps);
|
||||
expect(start.types.getAll().buckets.length).toBe(12);
|
||||
expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
|
||||
expect(start.types.getAll().metrics.length).toBe(22);
|
||||
expect(start.types.getAll().metrics.length).toBe(23);
|
||||
expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -134,6 +134,10 @@ export interface AggFunctionsMapping {
|
|||
//
|
||||
// (undocumented)
|
||||
aggFilter: ReturnType<typeof aggFilter>;
|
||||
// Warning: (ae-forgotten-export) The symbol "aggFilteredMetric" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
aggFilteredMetric: ReturnType<typeof aggFilteredMetric>;
|
||||
// Warning: (ae-forgotten-export) The symbol "aggFilters" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
@ -405,7 +409,7 @@ export const ES_SEARCH_STRATEGY = "es";
|
|||
// Warning: (ae-missing-release-tag) "EsaggsExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_34, Arguments_20, Output_34>;
|
||||
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input_35, Arguments_21, Output_35>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
@ -485,7 +489,7 @@ export type ExpressionFunctionKibana = ExpressionFunctionDefinition<'kibana', Ex
|
|||
// Warning: (ae-missing-release-tag) "ExpressionFunctionKibanaContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_21, Promise<KibanaContext>, ExecutionContext<Adapters, ExecutionContextSearch>>;
|
||||
export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<'kibana_context', KibanaContext | null, Arguments_22, Promise<KibanaContext>, ExecutionContext<Adapters, ExecutionContextSearch>>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ExpressionValueSearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
@ -1147,6 +1151,8 @@ export enum METRIC_TYPES {
|
|||
// (undocumented)
|
||||
DERIVATIVE = "derivative",
|
||||
// (undocumented)
|
||||
FILTERED_METRIC = "filtered_metric",
|
||||
// (undocumented)
|
||||
GEO_BOUNDS = "geo_bounds",
|
||||
// (undocumented)
|
||||
GEO_CENTROID = "geo_centroid",
|
||||
|
|
|
@ -63,6 +63,7 @@ export const createMetricVisTypeDefinition = (): VisTypeDefinition<VisParams> =>
|
|||
'!moving_avg',
|
||||
'!cumulative_sum',
|
||||
'!geo_bounds',
|
||||
'!filtered_metric',
|
||||
],
|
||||
aggSettings: {
|
||||
top_hits: {
|
||||
|
|
|
@ -50,7 +50,7 @@ export const tableVisLegacyTypeDefinition: VisTypeDefinition<TableVisParams> = {
|
|||
title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', {
|
||||
defaultMessage: 'Metric',
|
||||
}),
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
aggSettings: {
|
||||
top_hits: {
|
||||
allowStrings: true,
|
||||
|
|
|
@ -46,7 +46,7 @@ export const tableVisTypeDefinition: VisTypeDefinition<TableVisParams> = {
|
|||
title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', {
|
||||
defaultMessage: 'Metric',
|
||||
}),
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
aggSettings: {
|
||||
top_hits: {
|
||||
allowStrings: true,
|
||||
|
|
|
@ -51,6 +51,7 @@ export const tagCloudVisTypeDefinition = {
|
|||
'!derivative',
|
||||
'!geo_bounds',
|
||||
'!geo_centroid',
|
||||
'!filtered_metric',
|
||||
],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
|
|
|
@ -119,6 +119,7 @@ export const gaugeVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
|
|||
'!moving_avg',
|
||||
'!cumulative_sum',
|
||||
'!geo_bounds',
|
||||
'!filtered_metric',
|
||||
],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
|
|
|
@ -83,6 +83,7 @@ export const goalVisTypeDefinition: VisTypeDefinition<GaugeVisParams> = {
|
|||
'!moving_avg',
|
||||
'!cumulative_sum',
|
||||
'!geo_bounds',
|
||||
'!filtered_metric',
|
||||
],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
|
|
|
@ -94,6 +94,7 @@ export const heatmapVisTypeDefinition: VisTypeDefinition<HeatmapVisParams> = {
|
|||
'cardinality',
|
||||
'std_dev',
|
||||
'top_hits',
|
||||
'!filtered_metric',
|
||||
],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
|
|
|
@ -133,7 +133,7 @@ export const getAreaVisTypeDefinition = (
|
|||
title: i18n.translate('visTypeXy.area.metricsTitle', {
|
||||
defaultMessage: 'Y-axis',
|
||||
}),
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
min: 1,
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
|
|
|
@ -137,7 +137,7 @@ export const getHistogramVisTypeDefinition = (
|
|||
defaultMessage: 'Y-axis',
|
||||
}),
|
||||
min: 1,
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -136,7 +136,7 @@ export const getHorizontalBarVisTypeDefinition = (
|
|||
defaultMessage: 'Y-axis',
|
||||
}),
|
||||
min: 1,
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -132,7 +132,7 @@ export const getLineVisTypeDefinition = (
|
|||
name: 'metric',
|
||||
title: i18n.translate('visTypeXy.line.metricTitle', { defaultMessage: 'Y-axis' }),
|
||||
min: 1,
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds'],
|
||||
aggFilter: ['!geo_centroid', '!geo_bounds', '!filtered_metric'],
|
||||
defaults: [{ schema: 'metric', type: 'count' }],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiLink, EuiText, EuiPopover, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export function AdvancedOptions(props: {
|
||||
options: Array<{
|
||||
title: string;
|
||||
dataTestSubj: string;
|
||||
onClick: () => void;
|
||||
showInPopover: boolean;
|
||||
inlineElement: React.ReactElement | null;
|
||||
}>;
|
||||
}) {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const popoverOptions = props.options.filter((option) => option.showInPopover);
|
||||
const inlineOptions = props.options
|
||||
.filter((option) => option.inlineElement)
|
||||
.map((option) => React.cloneElement(option.inlineElement!, { key: option.dataTestSubj }));
|
||||
|
||||
return (
|
||||
<>
|
||||
{popoverOptions.length > 0 && (
|
||||
<EuiText textAlign="right">
|
||||
<EuiSpacer size="s" />
|
||||
<EuiPopover
|
||||
ownFocus
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
data-test-subj="indexPattern-advanced-popover"
|
||||
onClick={() => {
|
||||
setPopoverOpen(!popoverOpen);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.lens.indexPattern.advancedSettings', {
|
||||
defaultMessage: 'Add advanced options',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={() => {
|
||||
setPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
{popoverOptions.map(({ dataTestSubj, onClick, title }, index) => (
|
||||
<React.Fragment key={dataTestSubj}>
|
||||
<EuiText size="s">
|
||||
<EuiLink
|
||||
data-test-subj={dataTestSubj}
|
||||
color="text"
|
||||
onClick={() => {
|
||||
setPopoverOpen(false);
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
{popoverOptions.length - 1 !== index && <EuiSpacer size="s" />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</EuiPopover>
|
||||
</EuiText>
|
||||
)}
|
||||
{inlineOptions.length > 0 && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
{inlineOptions}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -32,6 +32,7 @@ import {
|
|||
resetIncomplete,
|
||||
FieldBasedIndexPatternColumn,
|
||||
canTransition,
|
||||
DEFAULT_TIME_SCALE,
|
||||
} from '../operations';
|
||||
import { mergeLayer } from '../state_helpers';
|
||||
import { FieldSelect } from './field_select';
|
||||
|
@ -41,7 +42,9 @@ import { IndexPattern, IndexPatternLayer } from '../types';
|
|||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
import { FormatSelector } from './format_selector';
|
||||
import { ReferenceEditor } from './reference_editor';
|
||||
import { TimeScaling } from './time_scaling';
|
||||
import { setTimeScaling, TimeScaling } from './time_scaling';
|
||||
import { defaultFilter, Filtering, setFilter } from './filtering';
|
||||
import { AdvancedOptions } from './advanced_options';
|
||||
|
||||
const operationPanels = getOperationDisplay();
|
||||
|
||||
|
@ -156,6 +159,8 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
.filter((type) => fieldByOperation[type]?.size || operationWithoutField.has(type));
|
||||
}, [fieldByOperation, operationWithoutField]);
|
||||
|
||||
const [filterByOpenInitially, setFilterByOpenInitally] = useState(false);
|
||||
|
||||
// Operations are compatible if they match inputs. They are always compatible in
|
||||
// the empty state. Field-based operations are not compatible with field-less operations.
|
||||
const operationsWithCompatibility = [...possibleOperations].map((operationType) => {
|
||||
|
@ -458,11 +463,63 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
)}
|
||||
|
||||
{!currentFieldIsInvalid && !incompleteInfo && selectedColumn && (
|
||||
<TimeScaling
|
||||
selectedColumn={selectedColumn}
|
||||
columnId={columnId}
|
||||
layer={state.layers[layerId]}
|
||||
updateLayer={setStateWrapper}
|
||||
<AdvancedOptions
|
||||
options={[
|
||||
{
|
||||
title: i18n.translate('xpack.lens.indexPattern.timeScale.enableTimeScale', {
|
||||
defaultMessage: 'Normalize by unit',
|
||||
}),
|
||||
dataTestSubj: 'indexPattern-time-scaling-enable',
|
||||
onClick: () => {
|
||||
setStateWrapper(
|
||||
setTimeScaling(columnId, state.layers[layerId], DEFAULT_TIME_SCALE)
|
||||
);
|
||||
},
|
||||
showInPopover: Boolean(
|
||||
operationDefinitionMap[selectedColumn.operationType].timeScalingMode &&
|
||||
operationDefinitionMap[selectedColumn.operationType].timeScalingMode !==
|
||||
'disabled' &&
|
||||
Object.values(state.layers[layerId].columns).some(
|
||||
(col) => col.operationType === 'date_histogram'
|
||||
) &&
|
||||
!selectedColumn.timeScale
|
||||
),
|
||||
inlineElement: (
|
||||
<TimeScaling
|
||||
selectedColumn={selectedColumn}
|
||||
columnId={columnId}
|
||||
layer={state.layers[layerId]}
|
||||
updateLayer={setStateWrapper}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.lens.indexPattern.filterBy.label', {
|
||||
defaultMessage: 'Filter by',
|
||||
}),
|
||||
dataTestSubj: 'indexPattern-filter-by-enable',
|
||||
onClick: () => {
|
||||
setFilterByOpenInitally(true);
|
||||
setStateWrapper(setFilter(columnId, state.layers[layerId], defaultFilter));
|
||||
},
|
||||
showInPopover: Boolean(
|
||||
operationDefinitionMap[selectedColumn.operationType].filterable &&
|
||||
!selectedColumn.filter
|
||||
),
|
||||
inlineElement:
|
||||
operationDefinitionMap[selectedColumn.operationType].filterable &&
|
||||
selectedColumn.filter ? (
|
||||
<Filtering
|
||||
indexPattern={currentIndexPattern}
|
||||
selectedColumn={selectedColumn}
|
||||
columnId={columnId}
|
||||
layer={state.layers[layerId]}
|
||||
updateLayer={setStateWrapper}
|
||||
isInitiallyOpen={filterByOpenInitially}
|
||||
/>
|
||||
) : null,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ReactWrapper, ShallowWrapper } from 'enzyme';
|
||||
import React, { ChangeEvent, MouseEvent } from 'react';
|
||||
import React, { ChangeEvent, MouseEvent, ReactElement } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
EuiComboBox,
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
EuiRange,
|
||||
EuiSelect,
|
||||
EuiButtonIcon,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
|
@ -30,10 +31,14 @@ import { documentField } from '../document_field';
|
|||
import { OperationMetadata } from '../../types';
|
||||
import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram';
|
||||
import { getFieldByNameFactory } from '../pure_helpers';
|
||||
import { TimeScaling } from './time_scaling';
|
||||
import { DimensionEditor } from './dimension_editor';
|
||||
import { AdvancedOptions } from './advanced_options';
|
||||
import { Filtering } from './filtering';
|
||||
|
||||
jest.mock('../loader');
|
||||
jest.mock('../query_input', () => ({
|
||||
QueryInput: () => null,
|
||||
}));
|
||||
jest.mock('../operations');
|
||||
jest.mock('lodash', () => {
|
||||
const original = jest.requireActual('lodash');
|
||||
|
@ -1029,7 +1034,7 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
}
|
||||
|
||||
it('should not show custom options if time scaling is not available', () => {
|
||||
wrapper = mount(
|
||||
wrapper = shallow(
|
||||
<IndexPatternDimensionEditorComponent
|
||||
{...getProps({
|
||||
operationType: 'avg',
|
||||
|
@ -1037,17 +1042,26 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
})}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="indexPattern-time-scaling"]')).toHaveLength(0);
|
||||
expect(
|
||||
wrapper
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-time-scaling-enable"]')
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should show custom options if time scaling is available', () => {
|
||||
wrapper = mount(<IndexPatternDimensionEditorComponent {...getProps({})} />);
|
||||
wrapper = shallow(<IndexPatternDimensionEditorComponent {...getProps({})} />);
|
||||
expect(
|
||||
wrapper
|
||||
.find(TimeScaling)
|
||||
.find('[data-test-subj="indexPattern-time-scaling-popover"]')
|
||||
.exists()
|
||||
).toBe(true);
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-time-scaling-enable"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should show current time scaling if set', () => {
|
||||
|
@ -1066,7 +1080,7 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
wrapper
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(TimeScaling)
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-time-scaling-enable"]')
|
||||
.prop('onClick')!({} as MouseEvent);
|
||||
|
@ -1239,6 +1253,199 @@ describe('IndexPatternDimensionEditorPanel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('filtering', () => {
|
||||
function getProps(colOverrides: Partial<IndexPatternColumn>) {
|
||||
return {
|
||||
...defaultProps,
|
||||
state: getStateWithColumns({
|
||||
datecolumn: {
|
||||
dataType: 'date',
|
||||
isBucketed: true,
|
||||
label: '',
|
||||
customLabel: true,
|
||||
operationType: 'date_histogram',
|
||||
sourceField: 'ts',
|
||||
params: {
|
||||
interval: '1d',
|
||||
},
|
||||
},
|
||||
col2: {
|
||||
dataType: 'number',
|
||||
isBucketed: false,
|
||||
label: 'Count of records',
|
||||
operationType: 'count',
|
||||
sourceField: 'Records',
|
||||
...colOverrides,
|
||||
} as IndexPatternColumn,
|
||||
}),
|
||||
columnId: 'col2',
|
||||
};
|
||||
}
|
||||
|
||||
it('should not show custom options if time scaling is not available', () => {
|
||||
wrapper = shallow(
|
||||
<IndexPatternDimensionEditorComponent
|
||||
{...getProps({
|
||||
operationType: 'terms',
|
||||
sourceField: 'bytes',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-filter-by-enable"]')
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should show custom options if filtering is available', () => {
|
||||
wrapper = shallow(<IndexPatternDimensionEditorComponent {...getProps({})} />);
|
||||
expect(
|
||||
wrapper
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-filter-by-enable"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should show current filter if set', () => {
|
||||
wrapper = mount(
|
||||
<IndexPatternDimensionEditorComponent
|
||||
{...getProps({ filter: { language: 'kuery', query: 'a: b' } })}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
(wrapper.find(Filtering).find(EuiPopover).prop('children') as ReactElement).props.value
|
||||
).toEqual({ language: 'kuery', query: 'a: b' });
|
||||
});
|
||||
|
||||
it('should allow to set filter initially', () => {
|
||||
const props = getProps({});
|
||||
wrapper = shallow(<IndexPatternDimensionEditorComponent {...props} />);
|
||||
wrapper
|
||||
.find(DimensionEditor)
|
||||
.dive()
|
||||
.find(AdvancedOptions)
|
||||
.dive()
|
||||
.find('[data-test-subj="indexPattern-filter-by-enable"]')
|
||||
.prop('onClick')!({} as MouseEvent);
|
||||
expect(props.setState).toHaveBeenCalledWith(
|
||||
{
|
||||
...props.state,
|
||||
layers: {
|
||||
first: {
|
||||
...props.state.layers.first,
|
||||
columns: {
|
||||
...props.state.layers.first.columns,
|
||||
col2: expect.objectContaining({
|
||||
filter: {
|
||||
language: 'kuery',
|
||||
query: '',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ shouldRemoveDimension: false, shouldReplaceDimension: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should carry over filter to other operation if possible', () => {
|
||||
const props = getProps({
|
||||
filter: { language: 'kuery', query: 'a: b' },
|
||||
sourceField: 'bytes',
|
||||
operationType: 'sum',
|
||||
label: 'Sum of bytes per hour',
|
||||
});
|
||||
wrapper = mount(<IndexPatternDimensionEditorComponent {...props} />);
|
||||
wrapper
|
||||
.find('button[data-test-subj="lns-indexPatternDimension-count incompatible"]')
|
||||
.simulate('click');
|
||||
expect(props.setState).toHaveBeenCalledWith(
|
||||
{
|
||||
...props.state,
|
||||
layers: {
|
||||
first: {
|
||||
...props.state.layers.first,
|
||||
columns: {
|
||||
...props.state.layers.first.columns,
|
||||
col2: expect.objectContaining({
|
||||
filter: { language: 'kuery', query: 'a: b' },
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ shouldRemoveDimension: false, shouldReplaceDimension: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow to change filter', () => {
|
||||
const props = getProps({
|
||||
filter: { language: 'kuery', query: 'a: b' },
|
||||
});
|
||||
wrapper = mount(<IndexPatternDimensionEditorComponent {...props} />);
|
||||
(wrapper.find(Filtering).find(EuiPopover).prop('children') as ReactElement).props.onChange({
|
||||
language: 'kuery',
|
||||
query: 'c: d',
|
||||
});
|
||||
expect(props.setState).toHaveBeenCalledWith(
|
||||
{
|
||||
...props.state,
|
||||
layers: {
|
||||
first: {
|
||||
...props.state.layers.first,
|
||||
columns: {
|
||||
...props.state.layers.first.columns,
|
||||
col2: expect.objectContaining({
|
||||
filter: { language: 'kuery', query: 'c: d' },
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ shouldRemoveDimension: false, shouldReplaceDimension: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow to remove filter', () => {
|
||||
const props = getProps({
|
||||
filter: { language: 'kuery', query: 'a: b' },
|
||||
});
|
||||
wrapper = mount(<IndexPatternDimensionEditorComponent {...props} />);
|
||||
wrapper
|
||||
.find('[data-test-subj="indexPattern-filter-by-remove"]')
|
||||
.find(EuiButtonIcon)
|
||||
.prop('onClick')!(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
{} as any
|
||||
);
|
||||
expect(props.setState).toHaveBeenCalledWith(
|
||||
{
|
||||
...props.state,
|
||||
layers: {
|
||||
first: {
|
||||
...props.state.layers.first,
|
||||
columns: {
|
||||
...props.state.layers.first.columns,
|
||||
col2: expect.objectContaining({
|
||||
filter: undefined,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ shouldRemoveDimension: false, shouldReplaceDimension: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render invalid field if field reference is broken', () => {
|
||||
wrapper = mount(
|
||||
<IndexPatternDimensionEditorComponent
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonIcon, EuiLink, EuiPanel, EuiPopover } from '@elastic/eui';
|
||||
import { EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { Query } from 'src/plugins/data/public';
|
||||
import { IndexPatternColumn, operationDefinitionMap } from '../operations';
|
||||
import { isQueryValid } from '../operations/definitions/filters';
|
||||
import { QueryInput } from '../query_input';
|
||||
import { IndexPattern, IndexPatternLayer } from '../types';
|
||||
|
||||
// to do: get the language from uiSettings
|
||||
export const defaultFilter: Query = {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
};
|
||||
|
||||
export function setFilter(columnId: string, layer: IndexPatternLayer, query: Query | undefined) {
|
||||
return {
|
||||
...layer,
|
||||
columns: {
|
||||
...layer.columns,
|
||||
[columnId]: {
|
||||
...layer.columns[columnId],
|
||||
filter: query,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function Filtering({
|
||||
selectedColumn,
|
||||
columnId,
|
||||
layer,
|
||||
updateLayer,
|
||||
indexPattern,
|
||||
isInitiallyOpen,
|
||||
}: {
|
||||
selectedColumn: IndexPatternColumn;
|
||||
indexPattern: IndexPattern;
|
||||
columnId: string;
|
||||
layer: IndexPatternLayer;
|
||||
updateLayer: (newLayer: IndexPatternLayer) => void;
|
||||
isInitiallyOpen: boolean;
|
||||
}) {
|
||||
const [filterPopoverOpen, setFilterPopoverOpen] = useState(isInitiallyOpen);
|
||||
const selectedOperation = operationDefinitionMap[selectedColumn.operationType];
|
||||
if (!selectedOperation.filterable || !selectedColumn.filter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isInvalid = !isQueryValid(selectedColumn.filter, indexPattern);
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.lens.indexPattern.filterBy.label', {
|
||||
defaultMessage: 'Filter by',
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
isOpen={filterPopoverOpen}
|
||||
closePopover={() => {
|
||||
setFilterPopoverOpen(false);
|
||||
}}
|
||||
anchorClassName="eui-fullWidth"
|
||||
panelClassName="lnsIndexPatternDimensionEditor__filtersEditor"
|
||||
button={
|
||||
<EuiPanel paddingSize="none">
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
className="lnsFiltersOperation__popoverButton"
|
||||
data-test-subj="indexPattern-filters-existingFilterTrigger"
|
||||
onClick={() => {
|
||||
setFilterPopoverOpen(!filterPopoverOpen);
|
||||
}}
|
||||
color={isInvalid ? 'danger' : 'text'}
|
||||
title={i18n.translate('xpack.lens.indexPattern.filterBy.clickToEdit', {
|
||||
defaultMessage: 'Click to edit',
|
||||
})}
|
||||
>
|
||||
{selectedColumn.filter.query ||
|
||||
i18n.translate('xpack.lens.indexPattern.filterBy.emptyFilterQuery', {
|
||||
defaultMessage: '(empty)',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
}
|
||||
>
|
||||
<QueryInput
|
||||
indexPattern={indexPattern}
|
||||
data-test-subj="indexPattern-filter-by-input"
|
||||
value={selectedColumn.filter || defaultFilter}
|
||||
onChange={(newQuery) => {
|
||||
updateLayer(setFilter(columnId, layer, newQuery));
|
||||
}}
|
||||
isInvalid={false}
|
||||
onSubmit={() => {}}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="indexPattern-filter-by-remove"
|
||||
color="danger"
|
||||
aria-label={i18n.translate('xpack.lens.filterBy.removeLabel', {
|
||||
defaultMessage: 'Remove filter',
|
||||
})}
|
||||
onClick={() => {
|
||||
updateLayer(setFilter(columnId, layer, undefined));
|
||||
}}
|
||||
iconType="cross"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -7,23 +7,11 @@
|
|||
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import {
|
||||
EuiLink,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiButtonIcon,
|
||||
EuiText,
|
||||
EuiPopover,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { EuiFormRow, EuiSelect, EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
adjustTimeScaleLabelSuffix,
|
||||
DEFAULT_TIME_SCALE,
|
||||
IndexPatternColumn,
|
||||
operationDefinitionMap,
|
||||
} from '../operations';
|
||||
|
@ -64,7 +52,6 @@ export function TimeScaling({
|
|||
layer: IndexPatternLayer;
|
||||
updateLayer: (newLayer: IndexPatternLayer) => void;
|
||||
}) {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const hasDateHistogram = layer.columnOrder.some(
|
||||
(colId) => layer.columns[colId].operationType === 'date_histogram'
|
||||
);
|
||||
|
@ -72,56 +59,12 @@ export function TimeScaling({
|
|||
if (
|
||||
!selectedOperation.timeScalingMode ||
|
||||
selectedOperation.timeScalingMode === 'disabled' ||
|
||||
!hasDateHistogram
|
||||
!hasDateHistogram ||
|
||||
!selectedColumn.timeScale
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectedColumn.timeScale) {
|
||||
return (
|
||||
<EuiText textAlign="right">
|
||||
<EuiSpacer size="s" />
|
||||
<EuiPopover
|
||||
ownFocus
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
data-test-subj="indexPattern-time-scaling-popover"
|
||||
onClick={() => {
|
||||
setPopoverOpen(!popoverOpen);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.lens.indexPattern.timeScale.advancedSettings', {
|
||||
defaultMessage: 'Add advanced options',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={() => {
|
||||
setPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<EuiText size="s">
|
||||
<EuiLink
|
||||
data-test-subj="indexPattern-time-scaling-enable"
|
||||
color="text"
|
||||
onClick={() => {
|
||||
setPopoverOpen(false);
|
||||
updateLayer(setTimeScaling(columnId, layer, DEFAULT_TIME_SCALE));
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.lens.indexPattern.timeScale.enableTimeScale', {
|
||||
defaultMessage: 'Normalize by unit',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
|
|
|
@ -521,6 +521,123 @@ describe('IndexPattern Data Source', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should wrap filtered metrics in filtered metric aggregation', async () => {
|
||||
const queryBaseState: IndexPatternBaseState = {
|
||||
currentIndexPatternId: '1',
|
||||
layers: {
|
||||
first: {
|
||||
indexPatternId: '1',
|
||||
columnOrder: ['col1', 'col2', 'col3'],
|
||||
columns: {
|
||||
col1: {
|
||||
label: 'Count of records',
|
||||
dataType: 'number',
|
||||
isBucketed: false,
|
||||
sourceField: 'Records',
|
||||
operationType: 'count',
|
||||
timeScale: 'h',
|
||||
filter: {
|
||||
language: 'kuery',
|
||||
query: 'bytes > 5',
|
||||
},
|
||||
},
|
||||
col2: {
|
||||
label: 'Average of bytes',
|
||||
dataType: 'number',
|
||||
isBucketed: false,
|
||||
sourceField: 'bytes',
|
||||
operationType: 'avg',
|
||||
timeScale: 'h',
|
||||
},
|
||||
col3: {
|
||||
label: 'Date',
|
||||
dataType: 'date',
|
||||
isBucketed: true,
|
||||
operationType: 'date_histogram',
|
||||
sourceField: 'timestamp',
|
||||
params: {
|
||||
interval: 'auto',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = enrichBaseState(queryBaseState);
|
||||
|
||||
const ast = indexPatternDatasource.toExpression(state, 'first') as Ast;
|
||||
expect(ast.chain[0].arguments.aggs[0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"customBucket": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"enabled": Array [
|
||||
true,
|
||||
],
|
||||
"filter": Array [
|
||||
"{\\"language\\":\\"kuery\\",\\"query\\":\\"bytes > 5\\"}",
|
||||
],
|
||||
"id": Array [
|
||||
"col1-filter",
|
||||
],
|
||||
"schema": Array [
|
||||
"bucket",
|
||||
],
|
||||
},
|
||||
"function": "aggFilter",
|
||||
"type": "function",
|
||||
},
|
||||
],
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
"customMetric": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"enabled": Array [
|
||||
true,
|
||||
],
|
||||
"id": Array [
|
||||
"col1-metric",
|
||||
],
|
||||
"schema": Array [
|
||||
"metric",
|
||||
],
|
||||
},
|
||||
"function": "aggCount",
|
||||
"type": "function",
|
||||
},
|
||||
],
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
"enabled": Array [
|
||||
true,
|
||||
],
|
||||
"id": Array [
|
||||
"col1",
|
||||
],
|
||||
"schema": Array [
|
||||
"metric",
|
||||
],
|
||||
},
|
||||
"function": "aggFilteredMetric",
|
||||
"type": "function",
|
||||
},
|
||||
],
|
||||
"type": "expression",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add time_scale and format function if time scale is set and supported', async () => {
|
||||
const queryBaseState: IndexPatternBaseState = {
|
||||
currentIndexPatternId: '1',
|
||||
|
|
|
@ -82,6 +82,7 @@ export const counterRateOperation: OperationDefinition<
|
|||
scale: 'ratio',
|
||||
references: referenceIds,
|
||||
timeScale,
|
||||
filter: previousColumn?.filter,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
};
|
||||
},
|
||||
|
@ -106,4 +107,5 @@ export const counterRateOperation: OperationDefinition<
|
|||
)?.join(', ');
|
||||
},
|
||||
timeScalingMode: 'mandatory',
|
||||
filterable: true,
|
||||
};
|
||||
|
|
|
@ -77,6 +77,7 @@ export const cumulativeSumOperation: OperationDefinition<
|
|||
operationType: 'cumulative_sum',
|
||||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
filter: previousColumn?.filter,
|
||||
references: referenceIds,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
};
|
||||
|
@ -101,4 +102,5 @@ export const cumulativeSumOperation: OperationDefinition<
|
|||
})
|
||||
)?.join(', ');
|
||||
},
|
||||
filterable: true,
|
||||
};
|
||||
|
|
|
@ -83,6 +83,7 @@ export const derivativeOperation: OperationDefinition<
|
|||
scale: 'ratio',
|
||||
references: referenceIds,
|
||||
timeScale: previousColumn?.timeScale,
|
||||
filter: previousColumn?.filter,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
};
|
||||
},
|
||||
|
@ -108,4 +109,5 @@ export const derivativeOperation: OperationDefinition<
|
|||
)?.join(', ');
|
||||
},
|
||||
timeScalingMode: 'optional',
|
||||
filterable: true,
|
||||
};
|
||||
|
|
|
@ -89,6 +89,7 @@ export const movingAverageOperation: OperationDefinition<
|
|||
scale: 'ratio',
|
||||
references: referenceIds,
|
||||
timeScale: previousColumn?.timeScale,
|
||||
filter: previousColumn?.filter,
|
||||
params: {
|
||||
window: 5,
|
||||
...getFormatFromPreviousColumn(previousColumn),
|
||||
|
@ -119,6 +120,7 @@ export const movingAverageOperation: OperationDefinition<
|
|||
)?.join(', ');
|
||||
},
|
||||
timeScalingMode: 'optional',
|
||||
filterable: true,
|
||||
};
|
||||
|
||||
function MovingAverageParamEditor({
|
||||
|
|
|
@ -70,6 +70,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo
|
|||
(!newField.aggregationRestrictions || newField.aggregationRestrictions.cardinality)
|
||||
);
|
||||
},
|
||||
filterable: true,
|
||||
getDefaultLabel: (column, indexPattern) => ofName(getSafeName(column.sourceField, indexPattern)),
|
||||
buildColumn({ field, previousColumn }) {
|
||||
return {
|
||||
|
@ -79,6 +80,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo
|
|||
scale: SCALE,
|
||||
sourceField: field.name,
|
||||
isBucketed: IS_BUCKETED,
|
||||
filter: previousColumn?.filter,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
};
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Query } from 'src/plugins/data/public';
|
||||
import type { Operation } from '../../../types';
|
||||
import { TimeScaleUnit } from '../../time_scale';
|
||||
import type { OperationType } from '../definitions';
|
||||
|
@ -14,6 +15,7 @@ export interface BaseIndexPatternColumn extends Operation {
|
|||
operationType: string;
|
||||
customLabel?: boolean;
|
||||
timeScale?: TimeScaleUnit;
|
||||
filter?: Query;
|
||||
}
|
||||
|
||||
// Formatting can optionally be added to any column
|
||||
|
|
|
@ -61,6 +61,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field
|
|||
scale: 'ratio',
|
||||
sourceField: field.name,
|
||||
timeScale: previousColumn?.timeScale,
|
||||
filter: previousColumn?.filter,
|
||||
params:
|
||||
previousColumn?.dataType === 'number' &&
|
||||
previousColumn.params &&
|
||||
|
@ -87,4 +88,5 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field
|
|||
return true;
|
||||
},
|
||||
timeScalingMode: 'optional',
|
||||
filterable: true,
|
||||
};
|
||||
|
|
|
@ -10,8 +10,9 @@ import { shallow, mount } from 'enzyme';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { EuiPopover, EuiLink } from '@elastic/eui';
|
||||
import { createMockedIndexPattern } from '../../../mocks';
|
||||
import { FilterPopover, QueryInput } from './filter_popover';
|
||||
import { FilterPopover } from './filter_popover';
|
||||
import { LabelInput } from '../shared_components';
|
||||
import { QueryInput } from '../../../query_input';
|
||||
|
||||
jest.mock('.', () => ({
|
||||
isQueryValid: () => true,
|
||||
|
|
|
@ -8,13 +8,12 @@
|
|||
import './filter_popover.scss';
|
||||
|
||||
import React, { MouseEventHandler, useEffect, useState } from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { EuiPopover, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FilterValue, defaultLabel, isQueryValid } from '.';
|
||||
import { IndexPattern } from '../../../types';
|
||||
import { QueryStringInput, Query } from '../../../../../../../../src/plugins/data/public';
|
||||
import { Query } from '../../../../../../../../src/plugins/data/public';
|
||||
import { LabelInput } from '../shared_components';
|
||||
import { QueryInput } from '../../../query_input';
|
||||
|
||||
export const FilterPopover = ({
|
||||
filter,
|
||||
|
@ -94,54 +93,3 @@ export const FilterPopover = ({
|
|||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export const QueryInput = ({
|
||||
value,
|
||||
onChange,
|
||||
indexPattern,
|
||||
isInvalid,
|
||||
onSubmit,
|
||||
}: {
|
||||
value: Query;
|
||||
onChange: (input: Query) => void;
|
||||
indexPattern: IndexPattern;
|
||||
isInvalid: boolean;
|
||||
onSubmit: () => void;
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
useDebounce(() => onChange(inputValue), 256, [inputValue]);
|
||||
|
||||
const handleInputChange = (input: Query) => {
|
||||
setInputValue(input);
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryStringInput
|
||||
dataTestSubj="indexPattern-filters-queryStringInput"
|
||||
size="s"
|
||||
isInvalid={isInvalid}
|
||||
bubbleSubmitEvent={false}
|
||||
indexPatterns={[indexPattern]}
|
||||
query={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onSubmit={() => {
|
||||
if (inputValue.query) {
|
||||
onSubmit();
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
inputValue.language === 'kuery'
|
||||
? i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderKql', {
|
||||
defaultMessage: '{example}',
|
||||
values: { example: 'method : "GET" or status : "404"' },
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderLucene', {
|
||||
defaultMessage: '{example}',
|
||||
values: { example: 'method:GET OR status:404' },
|
||||
})
|
||||
}
|
||||
languageSwitcherPopoverAnchorPosition="rightDown"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -230,6 +230,7 @@ interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn> {
|
|||
* If set to optional, time scaling won't be enabled by default and can be removed.
|
||||
*/
|
||||
timeScalingMode?: TimeScalingMode;
|
||||
filterable?: boolean;
|
||||
|
||||
getHelpMessage?: (props: HelpProps<C>) => React.ReactNode;
|
||||
}
|
||||
|
|
|
@ -161,12 +161,14 @@ export const lastValueOperation: OperationDefinition<LastValueIndexPatternColumn
|
|||
isBucketed: false,
|
||||
scale: field.type === 'string' ? 'ordinal' : 'ratio',
|
||||
sourceField: field.name,
|
||||
filter: previousColumn?.filter,
|
||||
params: {
|
||||
sortField,
|
||||
...getFormatFromPreviousColumn(previousColumn),
|
||||
},
|
||||
};
|
||||
},
|
||||
filterable: true,
|
||||
toEsAggsFn: (column, columnId) => {
|
||||
return buildExpressionFunction<AggFunctionsMapping['aggTopHit']>('aggTopHit', {
|
||||
id: columnId,
|
||||
|
|
|
@ -99,6 +99,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({
|
|||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
timeScale: optionalTimeScaling ? previousColumn?.timeScale : undefined,
|
||||
filter: previousColumn?.filter,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
} as T),
|
||||
onFieldChange: (oldColumn, field) => {
|
||||
|
@ -118,6 +119,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({
|
|||
},
|
||||
getErrorMessage: (layer, columnId, indexPattern) =>
|
||||
getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern),
|
||||
filterable: true,
|
||||
} as OperationDefinition<T, 'field'>;
|
||||
}
|
||||
|
||||
|
|
|
@ -967,6 +967,49 @@ describe('state_helpers', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should remove filter from the wrapped column if it gets wrapped (case new1)', () => {
|
||||
const expectedColumn = {
|
||||
label: 'Count',
|
||||
customLabel: true,
|
||||
dataType: 'number' as const,
|
||||
isBucketed: false,
|
||||
sourceField: 'Records',
|
||||
operationType: 'count' as const,
|
||||
};
|
||||
|
||||
const testFilter = { language: 'kuery', query: '' };
|
||||
|
||||
const layer: IndexPatternLayer = {
|
||||
indexPatternId: '1',
|
||||
columnOrder: ['col1'],
|
||||
columns: { col1: { ...expectedColumn, filter: testFilter } },
|
||||
};
|
||||
const result = replaceColumn({
|
||||
layer,
|
||||
indexPattern,
|
||||
columnId: 'col1',
|
||||
op: 'testReference' as OperationType,
|
||||
visualizationGroups: [],
|
||||
});
|
||||
|
||||
expect(operationDefinitionMap.testReference.buildColumn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
referenceIds: ['id1'],
|
||||
previousColumn: expect.objectContaining({
|
||||
// filter should be passed to the buildColumn function of the target operation
|
||||
filter: testFilter,
|
||||
}),
|
||||
})
|
||||
);
|
||||
expect(result.columns).toEqual(
|
||||
expect.objectContaining({
|
||||
// filter should be stripped from the original column
|
||||
id1: expectedColumn,
|
||||
col1: expect.any(Object),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a new no-input operation to use as reference (case new2)', () => {
|
||||
// @ts-expect-error this function is not valid
|
||||
operationDefinitionMap.testReference.requiredReferences = [
|
||||
|
|
|
@ -509,7 +509,18 @@ function applyReferenceTransition({
|
|||
if (!hasExactMatch && isColumnValidAsReference({ validation, column: previousColumn })) {
|
||||
hasExactMatch = true;
|
||||
|
||||
const newLayer = { ...layer, columns: { ...layer.columns, [newId]: { ...previousColumn } } };
|
||||
const newLayer = {
|
||||
...layer,
|
||||
columns: {
|
||||
...layer.columns,
|
||||
[newId]: {
|
||||
...previousColumn,
|
||||
// drop the filter for the referenced column because the wrapping operation
|
||||
// is filterable as well and will handle it one level higher.
|
||||
filter: operationDefinition.filterable ? undefined : previousColumn.filter,
|
||||
},
|
||||
},
|
||||
};
|
||||
layer = {
|
||||
...layer,
|
||||
columnOrder: getColumnOrder(newLayer),
|
||||
|
|
|
@ -32,6 +32,7 @@ export const createMockedReferenceOperation = () => {
|
|||
references: args.referenceIds,
|
||||
};
|
||||
}),
|
||||
filterable: true,
|
||||
isTransferable: jest.fn(),
|
||||
toExpression: jest.fn().mockReturnValue([]),
|
||||
getPossibleOperation: jest.fn().mockReturnValue({ dataType: 'number', isBucketed: false }),
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IndexPattern } from './types';
|
||||
import { QueryStringInput, Query } from '../../../../../src/plugins/data/public';
|
||||
|
||||
export const QueryInput = ({
|
||||
value,
|
||||
onChange,
|
||||
indexPattern,
|
||||
isInvalid,
|
||||
onSubmit,
|
||||
disableAutoFocus,
|
||||
}: {
|
||||
value: Query;
|
||||
onChange: (input: Query) => void;
|
||||
indexPattern: IndexPattern;
|
||||
isInvalid: boolean;
|
||||
onSubmit: () => void;
|
||||
disableAutoFocus?: boolean;
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
useDebounce(() => onChange(inputValue), 256, [inputValue]);
|
||||
|
||||
const handleInputChange = (input: Query) => {
|
||||
setInputValue(input);
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryStringInput
|
||||
dataTestSubj="indexPattern-filters-queryStringInput"
|
||||
size="s"
|
||||
disableAutoFocus={disableAutoFocus}
|
||||
isInvalid={isInvalid}
|
||||
bubbleSubmitEvent={false}
|
||||
indexPatterns={[indexPattern]}
|
||||
query={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onSubmit={() => {
|
||||
if (inputValue.query) {
|
||||
onSubmit();
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
inputValue.language === 'kuery'
|
||||
? i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderKql', {
|
||||
defaultMessage: '{example}',
|
||||
values: { example: 'method : "GET" or status : "404"' },
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPattern.filters.queryPlaceholderLucene', {
|
||||
defaultMessage: '{example}',
|
||||
values: { example: 'method:GET OR status:404' },
|
||||
})
|
||||
}
|
||||
languageSwitcherPopoverAnchorPosition="rightDown"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { IUiSettingsClient } from 'kibana/public';
|
||||
import {
|
||||
AggFunctionsMapping,
|
||||
EsaggsExpressionFunctionDefinition,
|
||||
IndexPatternLoadExpressionFunctionDefinition,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
|
@ -29,11 +30,32 @@ function getExpressionForLayer(
|
|||
indexPattern: IndexPattern,
|
||||
uiSettings: IUiSettingsClient
|
||||
): ExpressionAstExpression | null {
|
||||
const { columns, columnOrder } = layer;
|
||||
const { columnOrder } = layer;
|
||||
if (columnOrder.length === 0 || !indexPattern) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = { ...layer.columns };
|
||||
Object.keys(columns).forEach((columnId) => {
|
||||
const column = columns[columnId];
|
||||
const rootDef = operationDefinitionMap[column.operationType];
|
||||
if (
|
||||
'references' in column &&
|
||||
rootDef.filterable &&
|
||||
rootDef.input === 'fullReference' &&
|
||||
column.filter
|
||||
) {
|
||||
// inherit filter to all referenced operations
|
||||
column.references.forEach((referenceColumnId) => {
|
||||
const referencedColumn = columns[referenceColumnId];
|
||||
const referenceDef = operationDefinitionMap[column.operationType];
|
||||
if (referenceDef.filterable) {
|
||||
columns[referenceColumnId] = { ...referencedColumn, filter: column.filter };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const columnEntries = columnOrder.map((colId) => [colId, columns[colId]] as const);
|
||||
|
||||
if (columnEntries.length) {
|
||||
|
@ -44,10 +66,37 @@ function getExpressionForLayer(
|
|||
if (def.input === 'fullReference') {
|
||||
expressions.push(...def.toExpression(layer, colId, indexPattern));
|
||||
} else {
|
||||
const wrapInFilter = Boolean(def.filterable && col.filter);
|
||||
let aggAst = def.toEsAggsFn(
|
||||
col,
|
||||
wrapInFilter ? `${colId}-metric` : colId,
|
||||
indexPattern,
|
||||
layer,
|
||||
uiSettings
|
||||
);
|
||||
if (wrapInFilter) {
|
||||
aggAst = buildExpressionFunction<AggFunctionsMapping['aggFilteredMetric']>(
|
||||
'aggFilteredMetric',
|
||||
{
|
||||
id: colId,
|
||||
enabled: true,
|
||||
schema: 'metric',
|
||||
customBucket: buildExpression([
|
||||
buildExpressionFunction<AggFunctionsMapping['aggFilter']>('aggFilter', {
|
||||
id: `${colId}-filter`,
|
||||
enabled: true,
|
||||
schema: 'bucket',
|
||||
filter: JSON.stringify(col.filter),
|
||||
}),
|
||||
]),
|
||||
customMetric: buildExpression({ type: 'expression', chain: [aggAst] }),
|
||||
}
|
||||
).toAst();
|
||||
}
|
||||
aggs.push(
|
||||
buildExpression({
|
||||
type: 'expression',
|
||||
chain: [def.toEsAggsFn(col, colId, indexPattern, layer, uiSettings)],
|
||||
chain: [aggAst],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12087,7 +12087,6 @@
|
|||
"xpack.lens.indexPattern.terms.otherLabel": "その他",
|
||||
"xpack.lens.indexPattern.terms.size": "値の数",
|
||||
"xpack.lens.indexPattern.termsOf": "{name} のトップの値",
|
||||
"xpack.lens.indexPattern.timeScale.advancedSettings": "高度なオプションを追加",
|
||||
"xpack.lens.indexPattern.timeScale.enableTimeScale": "単位で正規化",
|
||||
"xpack.lens.indexPattern.timeScale.label": "単位で正規化",
|
||||
"xpack.lens.indexPattern.timeScale.tooltip": "基本の日付間隔に関係なく、常に指定された時間単位のレートとして表示されるように値を正規化します。",
|
||||
|
|
|
@ -12244,7 +12244,6 @@
|
|||
"xpack.lens.indexPattern.terms.otherLabel": "其他",
|
||||
"xpack.lens.indexPattern.terms.size": "值数目",
|
||||
"xpack.lens.indexPattern.termsOf": "{name} 排名最前值",
|
||||
"xpack.lens.indexPattern.timeScale.advancedSettings": "添加高级选项",
|
||||
"xpack.lens.indexPattern.timeScale.enableTimeScale": "按单位标准化",
|
||||
"xpack.lens.indexPattern.timeScale.label": "按单位标准化",
|
||||
"xpack.lens.indexPattern.timeScale.tooltip": "将值标准化为始终显示为每指定时间单位速率,无论基础日期时间间隔是多少。",
|
||||
|
|
Loading…
Reference in a new issue