mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
9647af5476
commit
658d6378c4
18 changed files with 970 additions and 0 deletions
|
@ -21,6 +21,7 @@ export interface ExpressionFunctionDefinitions
|
|||
| [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) | <code>ExpressionFunctionDerivative</code> | |
|
||||
| [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | <code>ExpressionFunctionFont</code> | |
|
||||
| [moving\_average](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md) | <code>ExpressionFunctionMovingAverage</code> | |
|
||||
| [overall\_metric](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md) | <code>ExpressionFunctionOverallMetric</code> | |
|
||||
| [theme](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.theme.md) | <code>ExpressionFunctionTheme</code> | |
|
||||
| [var\_set](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var_set.md) | <code>ExpressionFunctionVarSet</code> | |
|
||||
| [var](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var.md) | <code>ExpressionFunctionVar</code> | |
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [overall\_metric](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.overall_metric.md)
|
||||
|
||||
## ExpressionFunctionDefinitions.overall\_metric property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
overall_metric: ExpressionFunctionOverallMetric;
|
||||
```
|
|
@ -21,6 +21,7 @@ export interface ExpressionFunctionDefinitions
|
|||
| [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) | <code>ExpressionFunctionDerivative</code> | |
|
||||
| [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | <code>ExpressionFunctionFont</code> | |
|
||||
| [moving\_average](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md) | <code>ExpressionFunctionMovingAverage</code> | |
|
||||
| [overall\_metric](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md) | <code>ExpressionFunctionOverallMetric</code> | |
|
||||
| [theme](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.theme.md) | <code>ExpressionFunctionTheme</code> | |
|
||||
| [var\_set](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var_set.md) | <code>ExpressionFunctionVarSet</code> | |
|
||||
| [var](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var.md) | <code>ExpressionFunctionVar</code> | |
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [overall\_metric](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.overall_metric.md)
|
||||
|
||||
## ExpressionFunctionDefinitions.overall\_metric property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
overall_metric: ExpressionFunctionOverallMetric;
|
||||
```
|
|
@ -12,6 +12,7 @@ export * from './var_set';
|
|||
export * from './var';
|
||||
export * from './theme';
|
||||
export * from './cumulative_sum';
|
||||
export * from './overall_metric';
|
||||
export * from './derivative';
|
||||
export * from './moving_average';
|
||||
export * from './ui_setting';
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 { ExpressionFunctionDefinition } from '../types';
|
||||
import { Datatable } from '../../expression_types';
|
||||
import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers';
|
||||
|
||||
export interface OverallMetricArgs {
|
||||
by?: string[];
|
||||
inputColumnId: string;
|
||||
outputColumnId: string;
|
||||
outputColumnName?: string;
|
||||
metric: 'sum' | 'min' | 'max' | 'average';
|
||||
}
|
||||
|
||||
export type ExpressionFunctionOverallMetric = ExpressionFunctionDefinition<
|
||||
'overall_metric',
|
||||
Datatable,
|
||||
OverallMetricArgs,
|
||||
Datatable
|
||||
>;
|
||||
|
||||
function getValueAsNumberArray(value: unknown) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((innerVal) => Number(innerVal));
|
||||
} else {
|
||||
return [Number(value)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the overall metric of a specified column in the data table.
|
||||
*
|
||||
* Also supports multiple series in a single data table - use the `by` argument
|
||||
* to specify the columns to split the calculation by.
|
||||
* For each unique combination of all `by` columns a separate overall metric will be calculated.
|
||||
* The order of rows won't be changed - this function is not modifying any existing columns, it's only
|
||||
* adding the specified `outputColumnId` column to every row of the table without adding or removing rows.
|
||||
*
|
||||
* Behavior:
|
||||
* * Will write the overall metric of `inputColumnId` into `outputColumnId`
|
||||
* * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId`
|
||||
* * Each cell will contain the calculated metric based on the values of all cells belonging to the current series.
|
||||
*
|
||||
* Edge cases:
|
||||
* * Will return the input table if `inputColumnId` does not exist
|
||||
* * Will throw an error if `outputColumnId` exists already in provided data table
|
||||
* * If the row value contains `null` or `undefined`, it will be ignored and overwritten with the overall metric of
|
||||
* all cells of the same series.
|
||||
* * For all values besides `null` and `undefined`, the value will be cast to a number before it's added to the
|
||||
* overall metric of the current series - if this results in `NaN` (like in case of objects), all cells of the
|
||||
* current series will be set to `NaN`.
|
||||
* * To determine separate series defined by the `by` columns, the values of these columns will be cast to strings
|
||||
* before comparison. If the values are objects, the return value of their `toString` method will be used for comparison.
|
||||
* Missing values (`null` and `undefined`) will be treated as empty strings.
|
||||
*/
|
||||
export const overallMetric: ExpressionFunctionOverallMetric = {
|
||||
name: 'overall_metric',
|
||||
type: 'datatable',
|
||||
|
||||
inputTypes: ['datatable'],
|
||||
|
||||
help: i18n.translate('expressions.functions.overallMetric.help', {
|
||||
defaultMessage: 'Calculates the overall sum, min, max or average of a column in a data table',
|
||||
}),
|
||||
|
||||
args: {
|
||||
by: {
|
||||
help: i18n.translate('expressions.functions.overallMetric.args.byHelpText', {
|
||||
defaultMessage: 'Column to split the overall calculation by',
|
||||
}),
|
||||
multi: true,
|
||||
types: ['string'],
|
||||
required: false,
|
||||
},
|
||||
metric: {
|
||||
help: i18n.translate('expressions.functions.overallMetric.metricHelpText', {
|
||||
defaultMessage: 'Metric to calculate',
|
||||
}),
|
||||
types: ['string'],
|
||||
options: ['sum', 'min', 'max', 'average'],
|
||||
},
|
||||
inputColumnId: {
|
||||
help: i18n.translate('expressions.functions.overallMetric.args.inputColumnIdHelpText', {
|
||||
defaultMessage: 'Column to calculate the overall metric of',
|
||||
}),
|
||||
types: ['string'],
|
||||
required: true,
|
||||
},
|
||||
outputColumnId: {
|
||||
help: i18n.translate('expressions.functions.overallMetric.args.outputColumnIdHelpText', {
|
||||
defaultMessage: 'Column to store the resulting overall metric in',
|
||||
}),
|
||||
types: ['string'],
|
||||
required: true,
|
||||
},
|
||||
outputColumnName: {
|
||||
help: i18n.translate('expressions.functions.overallMetric.args.outputColumnNameHelpText', {
|
||||
defaultMessage: 'Name of the column to store the resulting overall metric in',
|
||||
}),
|
||||
types: ['string'],
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
fn(input, { by, inputColumnId, outputColumnId, outputColumnName, metric }) {
|
||||
const resultColumns = buildResultColumns(
|
||||
input,
|
||||
outputColumnId,
|
||||
inputColumnId,
|
||||
outputColumnName
|
||||
);
|
||||
|
||||
if (!resultColumns) {
|
||||
return input;
|
||||
}
|
||||
|
||||
const accumulators: Partial<Record<string, number>> = {};
|
||||
const valueCounter: Partial<Record<string, number>> = {};
|
||||
input.rows.forEach((row) => {
|
||||
const bucketIdentifier = getBucketIdentifier(row, by);
|
||||
const accumulatorValue = accumulators[bucketIdentifier] ?? 0;
|
||||
|
||||
const currentValue = row[inputColumnId];
|
||||
if (currentValue != null) {
|
||||
const currentNumberValues = getValueAsNumberArray(currentValue);
|
||||
switch (metric) {
|
||||
case 'average':
|
||||
valueCounter[bucketIdentifier] =
|
||||
(valueCounter[bucketIdentifier] ?? 0) + currentNumberValues.length;
|
||||
case 'sum':
|
||||
accumulators[bucketIdentifier] =
|
||||
accumulatorValue + currentNumberValues.reduce((a, b) => a + b, 0);
|
||||
break;
|
||||
case 'min':
|
||||
accumulators[bucketIdentifier] = Math.min(accumulatorValue, ...currentNumberValues);
|
||||
break;
|
||||
case 'max':
|
||||
accumulators[bucketIdentifier] = Math.max(accumulatorValue, ...currentNumberValues);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (metric === 'average') {
|
||||
Object.keys(accumulators).forEach((bucketIdentifier) => {
|
||||
accumulators[bucketIdentifier] =
|
||||
accumulators[bucketIdentifier]! / valueCounter[bucketIdentifier]!;
|
||||
});
|
||||
}
|
||||
return {
|
||||
...input,
|
||||
columns: resultColumns,
|
||||
rows: input.rows.map((row) => {
|
||||
const newRow = { ...row };
|
||||
const bucketIdentifier = getBucketIdentifier(row, by);
|
||||
newRow[outputColumnId] = accumulators[bucketIdentifier];
|
||||
|
||||
return newRow;
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* 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 './utils';
|
||||
import { ExecutionContext } from '../../../execution/types';
|
||||
import { Datatable } from '../../../expression_types/specs/datatable';
|
||||
import { overallMetric, OverallMetricArgs } from '../overall_metric';
|
||||
|
||||
describe('interpreter/functions#overall_metric', () => {
|
||||
const fn = functionWrapper(overallMetric);
|
||||
const runFn = (input: Datatable, args: OverallMetricArgs) =>
|
||||
fn(input, args, {} as ExecutionContext) as Datatable;
|
||||
|
||||
it('calculates overall sum', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'output',
|
||||
meta: { type: 'number' },
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([17, 17, 17, 17]);
|
||||
});
|
||||
|
||||
it('ignores null or undefined', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{}, { val: null }, { val: undefined }, { val: 1 }, { val: 5 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'output',
|
||||
meta: { type: 'number' },
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([3, 3, 3, 3, 3]);
|
||||
});
|
||||
|
||||
it('calculates overall sum for multiple series', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: 'A' },
|
||||
{ val: 2, split: 'B' },
|
||||
{ val: 3, split: 'B' },
|
||||
{ val: 4, split: 'A' },
|
||||
{ val: 5, split: 'A' },
|
||||
{ val: 6, split: 'A' },
|
||||
{ val: 7, split: 'B' },
|
||||
{ val: 8, split: 'B' },
|
||||
],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
|
||||
);
|
||||
|
||||
expect(result.rows.map((row) => row.output)).toEqual([
|
||||
1 + 4 + 5 + 6,
|
||||
2 + 3 + 7 + 8,
|
||||
2 + 3 + 7 + 8,
|
||||
1 + 4 + 5 + 6,
|
||||
1 + 4 + 5 + 6,
|
||||
1 + 4 + 5 + 6,
|
||||
2 + 3 + 7 + 8,
|
||||
2 + 3 + 7 + 8,
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats missing split column as separate series', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: 'A' },
|
||||
{ val: 2, split: 'B' },
|
||||
{ val: 3 },
|
||||
{ val: 4, split: 'A' },
|
||||
{ val: 5 },
|
||||
{ val: 6, split: 'A' },
|
||||
{ val: 7, split: 'B' },
|
||||
{ val: 8, split: 'B' },
|
||||
],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
|
||||
);
|
||||
expect(result.rows.map((row) => row.output)).toEqual([
|
||||
1 + 4 + 6,
|
||||
2 + 7 + 8,
|
||||
3 + 5,
|
||||
1 + 4 + 6,
|
||||
3 + 5,
|
||||
1 + 4 + 6,
|
||||
2 + 7 + 8,
|
||||
2 + 7 + 8,
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats null like undefined and empty string for split columns', () => {
|
||||
const table: Datatable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: 'A' },
|
||||
{ val: 2, split: 'B' },
|
||||
{ val: 3 },
|
||||
{ val: 4, split: 'A' },
|
||||
{ val: 5 },
|
||||
{ val: 6, split: 'A' },
|
||||
{ val: 7, split: null },
|
||||
{ val: 8, split: 'B' },
|
||||
{ val: 9, split: '' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = runFn(table, {
|
||||
inputColumnId: 'val',
|
||||
outputColumnId: 'output',
|
||||
by: ['split'],
|
||||
metric: 'sum',
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([
|
||||
1 + 4 + 6,
|
||||
2 + 8,
|
||||
3 + 5 + 7 + 9,
|
||||
1 + 4 + 6,
|
||||
3 + 5 + 7 + 9,
|
||||
1 + 4 + 6,
|
||||
3 + 5 + 7 + 9,
|
||||
2 + 8,
|
||||
3 + 5 + 7 + 9,
|
||||
]);
|
||||
|
||||
const result2 = runFn(table, {
|
||||
inputColumnId: 'val',
|
||||
outputColumnId: 'output',
|
||||
by: ['split'],
|
||||
metric: 'max',
|
||||
});
|
||||
expect(result2.rows.map((row) => row.output)).toEqual([6, 8, 9, 6, 9, 6, 9, 8, 9]);
|
||||
});
|
||||
|
||||
it('handles array values', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{ val: 5 }, { val: [7, 10] }, { val: [3, 1] }, { val: 2 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'output',
|
||||
meta: { type: 'number' },
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([28, 28, 28, 28]);
|
||||
});
|
||||
|
||||
it('takes array values into account for average calculation', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{ val: [3, 4] }, { val: 2 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'output',
|
||||
meta: { type: 'number' },
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([3, 3]);
|
||||
});
|
||||
|
||||
it('handles array values for split columns', () => {
|
||||
const table: Datatable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: 'A' },
|
||||
{ val: [2, 11], split: 'B' },
|
||||
{ val: 3 },
|
||||
{ val: 4, split: 'A' },
|
||||
{ val: 5 },
|
||||
{ val: 6, split: 'A' },
|
||||
{ val: 7, split: null },
|
||||
{ val: 8, split: 'B' },
|
||||
{ val: [9, 99], split: '' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = runFn(table, {
|
||||
inputColumnId: 'val',
|
||||
outputColumnId: 'output',
|
||||
by: ['split'],
|
||||
metric: 'sum',
|
||||
});
|
||||
expect(result.rows.map((row) => row.output)).toEqual([
|
||||
1 + 4 + 6,
|
||||
2 + 11 + 8,
|
||||
3 + 5 + 7 + 9 + 99,
|
||||
1 + 4 + 6,
|
||||
3 + 5 + 7 + 9 + 99,
|
||||
1 + 4 + 6,
|
||||
3 + 5 + 7 + 9 + 99,
|
||||
2 + 11 + 8,
|
||||
3 + 5 + 7 + 9 + 99,
|
||||
]);
|
||||
|
||||
const result2 = runFn(table, {
|
||||
inputColumnId: 'val',
|
||||
outputColumnId: 'output',
|
||||
by: ['split'],
|
||||
metric: 'max',
|
||||
});
|
||||
expect(result2.rows.map((row) => row.output)).toEqual([6, 11, 99, 6, 99, 6, 99, 11, 99]);
|
||||
});
|
||||
|
||||
it('calculates cumulative sum for multiple series by multiple split columns', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
{ id: 'split2', name: 'split2', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: 'A', split2: 'C' },
|
||||
{ val: 2, split: 'B', split2: 'C' },
|
||||
{ val: 3, split2: 'C' },
|
||||
{ val: 4, split: 'A', split2: 'C' },
|
||||
{ val: 5 },
|
||||
{ val: 6, split: 'A', split2: 'D' },
|
||||
{ val: 7, split: 'B', split2: 'D' },
|
||||
{ val: 8, split: 'B', split2: 'D' },
|
||||
],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', by: ['split', 'split2'], metric: 'sum' }
|
||||
);
|
||||
expect(result.rows.map((row) => row.output)).toEqual([1 + 4, 2, 3, 1 + 4, 5, 6, 7 + 8, 7 + 8]);
|
||||
});
|
||||
|
||||
it('splits separate series by the string representation of the cell values', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'val', name: 'val', meta: { type: 'number' } },
|
||||
{ id: 'split', name: 'split', meta: { type: 'string' } },
|
||||
],
|
||||
rows: [
|
||||
{ val: 1, split: { anObj: 3 } },
|
||||
{ val: 2, split: { anotherObj: 5 } },
|
||||
{ val: 10, split: 5 },
|
||||
{ val: 11, split: '5' },
|
||||
],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', by: ['split'], metric: 'sum' }
|
||||
);
|
||||
|
||||
expect(result.rows.map((row) => row.output)).toEqual([1 + 2, 1 + 2, 10 + 11, 10 + 11]);
|
||||
});
|
||||
|
||||
it('casts values to number before calculating cumulative sum', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'max' }
|
||||
);
|
||||
expect(result.rows.map((row) => row.output)).toEqual([7, 7, 7, 7]);
|
||||
});
|
||||
|
||||
it('casts values to number before calculating metric for NaN like values', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 2 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'min' }
|
||||
);
|
||||
expect(result.rows.map((row) => row.output)).toEqual([NaN, NaN, NaN, NaN]);
|
||||
});
|
||||
|
||||
it('skips undefined and null values', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
|
||||
rows: [
|
||||
{ val: null },
|
||||
{ val: 7 },
|
||||
{ val: undefined },
|
||||
{ val: undefined },
|
||||
{ val: undefined },
|
||||
{ val: undefined },
|
||||
{ val: '3' },
|
||||
{ val: 2 },
|
||||
{ val: null },
|
||||
],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'average' }
|
||||
);
|
||||
expect(result.rows.map((row) => row.output)).toEqual([4, 4, 4, 4, 4, 4, 4, 4, 4]);
|
||||
});
|
||||
|
||||
it('copies over meta information from the source column', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'val',
|
||||
name: 'val',
|
||||
meta: {
|
||||
type: 'number',
|
||||
|
||||
field: 'afield',
|
||||
index: 'anindex',
|
||||
params: { id: 'number', params: { pattern: '000' } },
|
||||
source: 'synthetic',
|
||||
sourceParams: {
|
||||
some: 'params',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [{ val: 5 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'output', metric: 'sum' }
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'output',
|
||||
meta: {
|
||||
type: 'number',
|
||||
|
||||
field: 'afield',
|
||||
index: 'anindex',
|
||||
params: { id: 'number', params: { pattern: '000' } },
|
||||
source: 'synthetic',
|
||||
sourceParams: {
|
||||
some: 'params',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets output name on output column if specified', () => {
|
||||
const result = runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'val',
|
||||
name: 'val',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [{ val: 5 }],
|
||||
},
|
||||
{
|
||||
inputColumnId: 'val',
|
||||
outputColumnId: 'output',
|
||||
outputColumnName: 'Output name',
|
||||
metric: 'min',
|
||||
}
|
||||
);
|
||||
expect(result.columns).toContainEqual({
|
||||
id: 'output',
|
||||
name: 'Output name',
|
||||
meta: { type: 'number' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns source table if input column does not exist', () => {
|
||||
const input: Datatable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'val',
|
||||
name: 'val',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [{ val: 5 }],
|
||||
};
|
||||
expect(
|
||||
runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output', metric: 'sum' })
|
||||
).toBe(input);
|
||||
});
|
||||
|
||||
it('throws an error if output column exists already', () => {
|
||||
expect(() =>
|
||||
runFn(
|
||||
{
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'val',
|
||||
name: 'val',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [{ val: 5 }],
|
||||
},
|
||||
{ inputColumnId: 'val', outputColumnId: 'val', metric: 'max' }
|
||||
)
|
||||
).toThrow();
|
||||
});
|
||||
});
|
|
@ -18,6 +18,7 @@ import {
|
|||
ExpressionFunctionCumulativeSum,
|
||||
ExpressionFunctionDerivative,
|
||||
ExpressionFunctionMovingAverage,
|
||||
ExpressionFunctionOverallMetric,
|
||||
} from './specs';
|
||||
import { ExpressionAstFunction } from '../ast';
|
||||
import { PersistableStateDefinition } from '../../../kibana_utils/common';
|
||||
|
@ -119,6 +120,7 @@ export interface ExpressionFunctionDefinitions {
|
|||
var: ExpressionFunctionVar;
|
||||
theme: ExpressionFunctionTheme;
|
||||
cumulative_sum: ExpressionFunctionCumulativeSum;
|
||||
overall_metric: ExpressionFunctionOverallMetric;
|
||||
derivative: ExpressionFunctionDerivative;
|
||||
moving_average: ExpressionFunctionMovingAverage;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
derivative,
|
||||
movingAverage,
|
||||
mapColumn,
|
||||
overallMetric,
|
||||
math,
|
||||
} from '../expression_functions';
|
||||
|
||||
|
@ -340,6 +341,7 @@ export class ExpressionsService implements PersistableStateService<ExpressionAst
|
|||
cumulativeSum,
|
||||
derivative,
|
||||
movingAverage,
|
||||
overallMetric,
|
||||
mapColumn,
|
||||
math,
|
||||
]) {
|
||||
|
|
|
@ -400,6 +400,10 @@ export interface ExpressionFunctionDefinitions {
|
|||
//
|
||||
// (undocumented)
|
||||
moving_average: ExpressionFunctionMovingAverage;
|
||||
// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionOverallMetric" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
overall_metric: ExpressionFunctionOverallMetric;
|
||||
// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionTheme" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
|
|
@ -372,6 +372,10 @@ export interface ExpressionFunctionDefinitions {
|
|||
//
|
||||
// (undocumented)
|
||||
moving_average: ExpressionFunctionMovingAverage;
|
||||
// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionOverallMetric" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
overall_metric: ExpressionFunctionOverallMetric;
|
||||
// Warning: (ae-forgotten-export) The symbol "ExpressionFunctionTheme" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
|
|
@ -146,6 +146,11 @@ export function DimensionEditor(props: DimensionEditorProps) {
|
|||
const possibleOperations = useMemo(() => {
|
||||
return Object.values(operationDefinitionMap)
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.filter(
|
||||
(operationDefinition) =>
|
||||
!('selectionStyle' in operationDefinition) ||
|
||||
operationDefinition.selectionStyle !== 'hidden'
|
||||
)
|
||||
.filter(({ type }) => fieldByOperation[type]?.size || operationWithoutField.has(type))
|
||||
.sort((op1, op2) => {
|
||||
return op1.displayName.localeCompare(op2.displayName);
|
||||
|
|
|
@ -9,3 +9,13 @@ export { counterRateOperation, CounterRateIndexPatternColumn } from './counter_r
|
|||
export { cumulativeSumOperation, CumulativeSumIndexPatternColumn } from './cumulative_sum';
|
||||
export { derivativeOperation, DerivativeIndexPatternColumn } from './differences';
|
||||
export { movingAverageOperation, MovingAverageIndexPatternColumn } from './moving_average';
|
||||
export {
|
||||
overallSumOperation,
|
||||
OverallSumIndexPatternColumn,
|
||||
overallMinOperation,
|
||||
OverallMinIndexPatternColumn,
|
||||
overallMaxOperation,
|
||||
OverallMaxIndexPatternColumn,
|
||||
overallAverageOperation,
|
||||
OverallAverageIndexPatternColumn,
|
||||
} from './overall_metric';
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
|
||||
import { optionallHistogramBasedOperationToExpression } from './utils';
|
||||
import { OperationDefinition } from '..';
|
||||
import { getFormatFromPreviousColumn } from '../helpers';
|
||||
|
||||
type OverallMetricIndexPatternColumn<T extends string> = FormattedIndexPatternColumn &
|
||||
ReferenceBasedIndexPatternColumn & {
|
||||
operationType: T;
|
||||
};
|
||||
|
||||
export type OverallSumIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_sum'>;
|
||||
export type OverallMinIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_min'>;
|
||||
export type OverallMaxIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_max'>;
|
||||
export type OverallAverageIndexPatternColumn = OverallMetricIndexPatternColumn<'overall_average'>;
|
||||
|
||||
function buildOverallMetricOperation<T extends OverallMetricIndexPatternColumn<string>>({
|
||||
type,
|
||||
displayName,
|
||||
ofName,
|
||||
description,
|
||||
metric,
|
||||
}: {
|
||||
type: T['operationType'];
|
||||
displayName: string;
|
||||
ofName: (name?: string) => string;
|
||||
description: string;
|
||||
metric: string;
|
||||
}): OperationDefinition<T, 'fullReference'> {
|
||||
return {
|
||||
type,
|
||||
priority: 1,
|
||||
displayName,
|
||||
input: 'fullReference',
|
||||
selectionStyle: 'hidden',
|
||||
requiredReferences: [
|
||||
{
|
||||
input: ['field', 'managedReference', 'fullReference'],
|
||||
validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
|
||||
},
|
||||
],
|
||||
getPossibleOperation: () => {
|
||||
return {
|
||||
dataType: 'number',
|
||||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
};
|
||||
},
|
||||
getDefaultLabel: (column, indexPattern, columns) => {
|
||||
const ref = columns[column.references[0]];
|
||||
return ofName(
|
||||
ref && 'sourceField' in ref
|
||||
? indexPattern.getFieldByName(ref.sourceField)?.displayName
|
||||
: undefined
|
||||
);
|
||||
},
|
||||
toExpression: (layer, columnId) => {
|
||||
return optionallHistogramBasedOperationToExpression(layer, columnId, 'overall_metric', {
|
||||
metric: [metric],
|
||||
});
|
||||
},
|
||||
buildColumn: ({ referenceIds, previousColumn, layer, indexPattern }, columnParams) => {
|
||||
const ref = layer.columns[referenceIds[0]];
|
||||
return {
|
||||
label: ofName(
|
||||
ref && 'sourceField' in ref
|
||||
? indexPattern.getFieldByName(ref.sourceField)?.displayName
|
||||
: undefined
|
||||
),
|
||||
dataType: 'number',
|
||||
operationType: 'overall_sum',
|
||||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
references: referenceIds,
|
||||
params: getFormatFromPreviousColumn(previousColumn),
|
||||
} as T;
|
||||
},
|
||||
isTransferable: () => {
|
||||
return true;
|
||||
},
|
||||
filterable: false,
|
||||
shiftable: false,
|
||||
documentation: {
|
||||
section: 'calculation',
|
||||
signature: i18n.translate('xpack.lens.indexPattern.overall_metric', {
|
||||
defaultMessage: 'metric: number',
|
||||
}),
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const overallSumOperation = buildOverallMetricOperation<OverallSumIndexPatternColumn>({
|
||||
type: 'overall_sum',
|
||||
displayName: i18n.translate('xpack.lens.indexPattern.overallSum', {
|
||||
defaultMessage: 'Overall sum',
|
||||
}),
|
||||
ofName: (name?: string) => {
|
||||
return i18n.translate('xpack.lens.indexPattern.overallSumOf', {
|
||||
defaultMessage: 'Overall sum of {name}',
|
||||
values: {
|
||||
name:
|
||||
name ??
|
||||
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
|
||||
defaultMessage: '(incomplete)',
|
||||
}),
|
||||
},
|
||||
});
|
||||
},
|
||||
metric: 'sum',
|
||||
description: i18n.translate('xpack.lens.indexPattern.overall_sum.documentation', {
|
||||
defaultMessage: `
|
||||
Calculates the sum of a metric of all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
|
||||
Other dimensions breaking down the data like top values or filter are treated as separate series.
|
||||
|
||||
If no date histograms or interval functions are used in the current chart, \`overall_sum\` is calculating the sum over all dimensions no matter the used function.
|
||||
|
||||
Example: Percentage of total
|
||||
\`sum(bytes) / overall_sum(sum(bytes))\`
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
export const overallMinOperation = buildOverallMetricOperation<OverallMinIndexPatternColumn>({
|
||||
type: 'overall_min',
|
||||
displayName: i18n.translate('xpack.lens.indexPattern.overallMin', {
|
||||
defaultMessage: 'Overall min',
|
||||
}),
|
||||
ofName: (name?: string) => {
|
||||
return i18n.translate('xpack.lens.indexPattern.overallMinOf', {
|
||||
defaultMessage: 'Overall min of {name}',
|
||||
values: {
|
||||
name:
|
||||
name ??
|
||||
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
|
||||
defaultMessage: '(incomplete)',
|
||||
}),
|
||||
},
|
||||
});
|
||||
},
|
||||
metric: 'min',
|
||||
description: i18n.translate('xpack.lens.indexPattern.overall_min.documentation', {
|
||||
defaultMessage: `
|
||||
Calculates the minimum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
|
||||
Other dimensions breaking down the data like top values or filter are treated as separate series.
|
||||
|
||||
If no date histograms or interval functions are used in the current chart, \`overall_min\` is calculating the minimum over all dimensions no matter the used function
|
||||
|
||||
Example: Percentage of range
|
||||
\`(sum(bytes) - overall_min(sum(bytes)) / (overall_max(bytes) - overall_min(bytes))\`
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
export const overallMaxOperation = buildOverallMetricOperation<OverallMaxIndexPatternColumn>({
|
||||
type: 'overall_max',
|
||||
displayName: i18n.translate('xpack.lens.indexPattern.overallMax', {
|
||||
defaultMessage: 'Overall max',
|
||||
}),
|
||||
ofName: (name?: string) => {
|
||||
return i18n.translate('xpack.lens.indexPattern.overallMaxOf', {
|
||||
defaultMessage: 'Overall max of {name}',
|
||||
values: {
|
||||
name:
|
||||
name ??
|
||||
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
|
||||
defaultMessage: '(incomplete)',
|
||||
}),
|
||||
},
|
||||
});
|
||||
},
|
||||
metric: 'max',
|
||||
description: i18n.translate('xpack.lens.indexPattern.overall_max.documentation', {
|
||||
defaultMessage: `
|
||||
Calculates the maximum of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
|
||||
Other dimensions breaking down the data like top values or filter are treated as separate series.
|
||||
|
||||
If no date histograms or interval functions are used in the current chart, \`overall_max\` is calculating the maximum over all dimensions no matter the used function
|
||||
|
||||
Example: Percentage of range
|
||||
\`(sum(bytes) - overall_min(sum(bytes)) / (overall_max(bytes) - overall_min(bytes))\`
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
export const overallAverageOperation = buildOverallMetricOperation<OverallAverageIndexPatternColumn>(
|
||||
{
|
||||
type: 'overall_average',
|
||||
displayName: i18n.translate('xpack.lens.indexPattern.overallMax', {
|
||||
defaultMessage: 'Overall max',
|
||||
}),
|
||||
ofName: (name?: string) => {
|
||||
return i18n.translate('xpack.lens.indexPattern.overallAverageOf', {
|
||||
defaultMessage: 'Overall average of {name}',
|
||||
values: {
|
||||
name:
|
||||
name ??
|
||||
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
|
||||
defaultMessage: '(incomplete)',
|
||||
}),
|
||||
},
|
||||
});
|
||||
},
|
||||
metric: 'average',
|
||||
description: i18n.translate('xpack.lens.indexPattern.overall_average.documentation', {
|
||||
defaultMessage: `
|
||||
Calculates the average of a metric for all data points of a series in the current chart. A series is defined by a dimension using a date histogram or interval function.
|
||||
Other dimensions breaking down the data like top values or filter are treated as separate series.
|
||||
|
||||
If no date histograms or interval functions are used in the current chart, \`overall_average\` is calculating the average over all dimensions no matter the used function
|
||||
|
||||
Example: Divergence from the mean:
|
||||
\`sum(bytes) - overall_average(sum(bytes))\`
|
||||
`,
|
||||
}),
|
||||
}
|
||||
);
|
|
@ -134,3 +134,35 @@ export function dateBasedOperationToExpression(
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an expression ast for a date based operation (cumulative sum, derivative, moving average, counter rate)
|
||||
*/
|
||||
export function optionallHistogramBasedOperationToExpression(
|
||||
layer: IndexPatternLayer,
|
||||
columnId: string,
|
||||
functionName: string,
|
||||
additionalArgs: Record<string, unknown[]> = {}
|
||||
): ExpressionFunctionAST[] {
|
||||
const currentColumn = (layer.columns[columnId] as unknown) as ReferenceBasedIndexPatternColumn;
|
||||
const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed);
|
||||
const nonHistogramColumns = buckets.filter(
|
||||
(colId) =>
|
||||
layer.columns[colId].operationType !== 'date_histogram' &&
|
||||
layer.columns[colId].operationType !== 'range'
|
||||
)!;
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'function',
|
||||
function: functionName,
|
||||
arguments: {
|
||||
by: nonHistogramColumns.length === buckets.length ? [] : nonHistogramColumns,
|
||||
inputColumnId: [currentColumn.references[0]],
|
||||
outputColumnId: [columnId],
|
||||
outputColumnName: [currentColumn.label],
|
||||
...additionalArgs,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -33,6 +33,14 @@ import {
|
|||
DerivativeIndexPatternColumn,
|
||||
movingAverageOperation,
|
||||
MovingAverageIndexPatternColumn,
|
||||
OverallSumIndexPatternColumn,
|
||||
overallSumOperation,
|
||||
OverallMinIndexPatternColumn,
|
||||
overallMinOperation,
|
||||
OverallMaxIndexPatternColumn,
|
||||
overallMaxOperation,
|
||||
OverallAverageIndexPatternColumn,
|
||||
overallAverageOperation,
|
||||
} from './calculations';
|
||||
import { countOperation, CountIndexPatternColumn } from './count';
|
||||
import {
|
||||
|
@ -71,6 +79,10 @@ export type IndexPatternColumn =
|
|||
| CountIndexPatternColumn
|
||||
| LastValueIndexPatternColumn
|
||||
| CumulativeSumIndexPatternColumn
|
||||
| OverallSumIndexPatternColumn
|
||||
| OverallMinIndexPatternColumn
|
||||
| OverallMaxIndexPatternColumn
|
||||
| OverallAverageIndexPatternColumn
|
||||
| CounterRateIndexPatternColumn
|
||||
| DerivativeIndexPatternColumn
|
||||
| MovingAverageIndexPatternColumn
|
||||
|
@ -98,6 +110,10 @@ export {
|
|||
CounterRateIndexPatternColumn,
|
||||
DerivativeIndexPatternColumn,
|
||||
MovingAverageIndexPatternColumn,
|
||||
OverallSumIndexPatternColumn,
|
||||
OverallMinIndexPatternColumn,
|
||||
OverallMaxIndexPatternColumn,
|
||||
OverallAverageIndexPatternColumn,
|
||||
} from './calculations';
|
||||
export { CountIndexPatternColumn } from './count';
|
||||
export { LastValueIndexPatternColumn } from './last_value';
|
||||
|
@ -126,6 +142,10 @@ const internalOperationDefinitions = [
|
|||
movingAverageOperation,
|
||||
mathOperation,
|
||||
formulaOperation,
|
||||
overallSumOperation,
|
||||
overallMinOperation,
|
||||
overallMaxOperation,
|
||||
overallAverageOperation,
|
||||
];
|
||||
|
||||
export { termsOperation } from './terms';
|
||||
|
@ -141,6 +161,10 @@ export {
|
|||
counterRateOperation,
|
||||
derivativeOperation,
|
||||
movingAverageOperation,
|
||||
overallSumOperation,
|
||||
overallAverageOperation,
|
||||
overallMaxOperation,
|
||||
overallMinOperation,
|
||||
} from './calculations';
|
||||
export { formulaOperation } from './formula/formula';
|
||||
|
||||
|
|
|
@ -31,4 +31,8 @@ export {
|
|||
CounterRateIndexPatternColumn,
|
||||
DerivativeIndexPatternColumn,
|
||||
MovingAverageIndexPatternColumn,
|
||||
OverallSumIndexPatternColumn,
|
||||
OverallMinIndexPatternColumn,
|
||||
OverallMaxIndexPatternColumn,
|
||||
OverallAverageIndexPatternColumn,
|
||||
} from './definitions';
|
||||
|
|
|
@ -319,6 +319,22 @@ describe('getOperationTypesForField', () => {
|
|||
"operationType": "moving_average",
|
||||
"type": "fullReference",
|
||||
},
|
||||
Object {
|
||||
"operationType": "overall_sum",
|
||||
"type": "fullReference",
|
||||
},
|
||||
Object {
|
||||
"operationType": "overall_min",
|
||||
"type": "fullReference",
|
||||
},
|
||||
Object {
|
||||
"operationType": "overall_max",
|
||||
"type": "fullReference",
|
||||
},
|
||||
Object {
|
||||
"operationType": "overall_average",
|
||||
"type": "fullReference",
|
||||
},
|
||||
Object {
|
||||
"field": "bytes",
|
||||
"operationType": "min",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue