From b87e47bdce25b2e560979d0ae532cbb4342ae2e7 Mon Sep 17 00:00:00 2001
From: Bena Kansara <69037875+benakansara@users.noreply.github.com>
Date: Mon, 18 Nov 2024 11:45:35 +0100
Subject: [PATCH] [Custom threshold/Metric threshold] [Preview chart] Sort
groups by first metric and aggType combination (#199643)
Resolves https://github.com/elastic/kibana/issues/183491
This PR sorts the data in Preview chart of Custom threshold and Metric
threshold rules based on first metric and aggType combination used in
the rule equation. For `rate`, `percentile` and `last_value`
aggregations, I have used `max` aggregation as those aggregations
require additional params to pass to `LensAttributesBuilder` which are
not supported currently. Also, sorting based on equation is not
supported right now.
| Before | After |
| --- | --- |
|
|
|
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../attribute_builder/utils.ts | 2 +-
.../rule_condition_chart/helpers.test.ts | 64 +++++-
.../rule_condition_chart/helpers.ts | 28 ++-
.../painless_tinymath_parser.test.ts | 216 +++++++++++++++---
.../painless_tinymath_parser.ts | 9 +-
.../rule_condition_chart.tsx | 32 ++-
6 files changed, 288 insertions(+), 63 deletions(-)
diff --git a/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts b/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts
index 6418023586084..9c6ea500c97c7 100644
--- a/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts
+++ b/packages/kbn-lens-embeddable-utils/attribute_builder/utils.ts
@@ -28,7 +28,7 @@ export type DateHistogramColumnParams = DateHistogramIndexPatternColumn['params'
export type TopValuesColumnParams = Pick<
TermsIndexPatternColumn['params'],
- 'size' | 'orderDirection' | 'orderBy' | 'secondaryFields' | 'accuracyMode'
+ 'size' | 'orderDirection' | 'orderBy' | 'secondaryFields' | 'accuracyMode' | 'orderAgg'
>;
export const getHistogramColumn = ({
diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts
index 044b57c64da28..a4825c11f85cd 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts
+++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts
@@ -17,7 +17,11 @@ const useCases = [
filter: '',
name: '',
},
- 'sum(system.cpu.user.pct)',
+ {
+ operation: 'sum',
+ operationWithField: 'sum(system.cpu.user.pct)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -26,7 +30,11 @@ const useCases = [
filter: '',
name: '',
},
- 'max(system.cpu.user.pct)',
+ {
+ operation: 'max',
+ operationWithField: 'max(system.cpu.user.pct)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -35,7 +43,11 @@ const useCases = [
filter: '',
name: '',
},
- 'min(system.cpu.user.pct)',
+ {
+ operation: 'min',
+ operationWithField: 'min(system.cpu.user.pct)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -44,16 +56,24 @@ const useCases = [
filter: '',
name: '',
},
- 'average(system.cpu.user.pct)',
+ {
+ operation: 'average',
+ operationWithField: 'average(system.cpu.user.pct)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
aggType: Aggregators.COUNT,
- field: 'system.cpu.user.pct',
- filter: '',
+ field: '',
+ filter: 'system.cpu.user.pct: *',
name: '',
},
- 'count(___records___)',
+ {
+ operation: 'count',
+ operationWithField: `count(kql='system.cpu.user.pct: *')`,
+ sourceField: '',
+ },
],
[
{
@@ -62,7 +82,11 @@ const useCases = [
filter: '',
name: '',
},
- 'unique_count(system.cpu.user.pct)',
+ {
+ operation: 'unique_count',
+ operationWithField: 'unique_count(system.cpu.user.pct)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -71,7 +95,11 @@ const useCases = [
filter: '',
name: '',
},
- 'percentile(system.cpu.user.pct, percentile=95)',
+ {
+ operation: 'percentile',
+ operationWithField: 'percentile(system.cpu.user.pct, percentile=95)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -80,7 +108,11 @@ const useCases = [
filter: '',
name: '',
},
- 'percentile(system.cpu.user.pct, percentile=99)',
+ {
+ operation: 'percentile',
+ operationWithField: 'percentile(system.cpu.user.pct, percentile=99)',
+ sourceField: 'system.cpu.user.pct',
+ },
],
[
{
@@ -89,7 +121,11 @@ const useCases = [
filter: '',
name: '',
},
- `counter_rate(max(system.network.in.bytes), kql='')`,
+ {
+ operation: 'counter_rate',
+ operationWithField: `counter_rate(max(system.network.in.bytes), kql='')`,
+ sourceField: 'system.network.in.bytes',
+ },
],
[
{
@@ -98,7 +134,11 @@ const useCases = [
filter: 'host.name : "foo"',
name: '',
},
- `counter_rate(max(system.network.in.bytes), kql='host.name : foo')`,
+ {
+ operation: 'counter_rate',
+ operationWithField: `counter_rate(max(system.network.in.bytes), kql='host.name : foo')`,
+ sourceField: 'system.network.in.bytes',
+ },
],
];
diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts
index a487f4899ad06..9eb52af738ea9 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts
+++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts
@@ -8,14 +8,24 @@
import { Aggregators } from '../../../common/custom_threshold_rule/types';
import { GenericMetric } from './rule_condition_chart';
-export const getLensOperationFromRuleMetric = (metric: GenericMetric): string => {
+export interface LensOperation {
+ operation: string;
+ operationWithField: string;
+ sourceField: string;
+}
+
+export const getLensOperationFromRuleMetric = (metric: GenericMetric): LensOperation => {
const { aggType, field, filter } = metric;
let operation: string = aggType;
const operationArgs: string[] = [];
const aggFilter = JSON.stringify(filter || '').replace(/"|\\/g, '');
if (aggType === Aggregators.RATE) {
- return `counter_rate(max(${field}), kql='${aggFilter}')`;
+ return {
+ operation: 'counter_rate',
+ operationWithField: `counter_rate(max(${field}), kql='${aggFilter}')`,
+ sourceField: field || '',
+ };
}
if (aggType === Aggregators.AVERAGE) operation = 'average';
@@ -23,14 +33,10 @@ export const getLensOperationFromRuleMetric = (metric: GenericMetric): string =>
if (aggType === Aggregators.P95 || aggType === Aggregators.P99) operation = 'percentile';
if (aggType === Aggregators.COUNT) operation = 'count';
- let sourceField = field;
-
- if (aggType === Aggregators.COUNT) {
- sourceField = '___records___';
+ if (field) {
+ operationArgs.push(field);
}
- operationArgs.push(sourceField || '');
-
if (aggType === Aggregators.P95) {
operationArgs.push('percentile=95');
}
@@ -41,7 +47,11 @@ export const getLensOperationFromRuleMetric = (metric: GenericMetric): string =>
if (aggFilter) operationArgs.push(`kql='${aggFilter}'`);
- return operation + '(' + operationArgs.join(', ') + ')';
+ return {
+ operation,
+ operationWithField: `${operation}(${operationArgs.join(', ')})`,
+ sourceField: field || '',
+ };
};
export const getBufferThreshold = (threshold?: number): string =>
diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts
index a957c58167403..647791cab5b8d 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts
+++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts
@@ -17,7 +17,11 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -28,7 +32,11 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- 'ABC-abc': 'average(system.cpu.system.pct)',
+ 'ABC-abc': {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
},
});
expect(parser.parse()).toEqual('100*average(system.cpu.system.pct)');
@@ -38,8 +46,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -52,9 +68,21 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
- C: 'average(system.cpu.cores)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
+ C: {
+ operationWithField: 'average(system.cpu.cores)',
+ operation: 'average',
+ sourceField: 'system.cpu.cores',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -67,7 +95,11 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -79,8 +111,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -93,8 +133,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -107,8 +155,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -121,8 +177,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -135,8 +199,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -149,8 +221,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -163,8 +243,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -178,8 +266,16 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- aa: 'average(system.cpu.system.pct)',
- baa: 'average(system.cpu.user.pct)',
+ aa: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ baa: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
},
});
expect(parser.parse()).toEqual(
@@ -193,12 +289,36 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
- C: 'average(system.cpu.total.pct)',
- D: 'average(system.cpu.cores)',
- E: 'count()',
- F: 'sum(system.cpu.total.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
+ C: {
+ operationWithField: 'average(system.cpu.total.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.total.pct',
+ },
+ D: {
+ operationWithField: 'average(system.cpu.cores)',
+ operation: 'average',
+ sourceField: 'system.cpu.cores',
+ },
+ E: {
+ operationWithField: 'count()',
+ operation: 'count',
+ sourceField: '',
+ },
+ F: {
+ operationWithField: 'sum(system.cpu.total.pct)',
+ operation: 'sum',
+ sourceField: 'system.cpu.total.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
@@ -213,12 +333,36 @@ describe('PainlessTinyMathParser', () => {
const parser = new PainlessTinyMathParser({
equation,
aggMap: {
- A: 'average(system.cpu.system.pct)',
- B: 'average(system.cpu.user.pct)',
- C: 'average(system.cpu.total.pct)',
- D: 'average(system.cpu.cores)',
- E: 'count()',
- F: 'sum(system.cpu.total.pct)',
+ A: {
+ operationWithField: 'average(system.cpu.system.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.system.pct',
+ },
+ B: {
+ operationWithField: 'average(system.cpu.user.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.user.pct',
+ },
+ C: {
+ operationWithField: 'average(system.cpu.total.pct)',
+ operation: 'average',
+ sourceField: 'system.cpu.total.pct',
+ },
+ D: {
+ operationWithField: 'average(system.cpu.cores)',
+ operation: 'average',
+ sourceField: 'system.cpu.cores',
+ },
+ E: {
+ operationWithField: 'count()',
+ operation: 'count',
+ sourceField: '',
+ },
+ F: {
+ operationWithField: 'sum(system.cpu.total.pct)',
+ operation: 'sum',
+ sourceField: 'system.cpu.total.pct',
+ },
},
});
// ✅ checked with Lens Formula editor
diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts
index 25ec15d3a808b..8c09ba029c579 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts
+++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { LensOperation } from './helpers';
+
// This is a parser of a subset operations/expression/statement of Painless A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, = to be used in Lens formula editor that uses TinyMath
// The goal is to parse painless expressions to a format that can be used in Lens formula editor
// The parser will also replace the characters A-Z with the values from aggMap
@@ -13,7 +15,7 @@
// This parser is using a simple recursive function to parse the expression and replace the characters with the values from aggMap
export interface AggMap {
- [key: string]: string;
+ [key: string]: LensOperation;
}
interface PainlessTinyMathParserProps {
equation: string;
@@ -81,7 +83,10 @@ export class PainlessTinyMathParser {
.sort()
.reverse()
.forEach((metricName) => {
- parsedInputString = parsedInputString.replaceAll(metricName, aggMap[metricName]);
+ parsedInputString = parsedInputString.replaceAll(
+ metricName,
+ aggMap[metricName].operationWithField
+ );
});
return parsedInputString;
diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx
index 4567d7c37b10b..2a9fa2c295274 100644
--- a/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx
+++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx
@@ -8,7 +8,7 @@
import React, { useState, useEffect } from 'react';
import { EuiEmptyPrompt, useEuiTheme } from '@elastic/eui';
import { Query, Filter } from '@kbn/es-query';
-import { FillStyle, SeriesType } from '@kbn/lens-plugin/public';
+import { FillStyle, SeriesType, TermsIndexPatternColumn } from '@kbn/lens-plugin/public';
import { DataView } from '@kbn/data-views-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import useAsync from 'react-use/lib/useAsync';
@@ -82,6 +82,10 @@ export interface RuleConditionChartProps {
additionalFilters?: Filter[];
}
+export type TopValuesOrderParams =
+ | Pick
+ | undefined;
+
const defaultQuery: Query = {
language: 'kuery',
query: '',
@@ -299,10 +303,10 @@ export function RuleConditionChart({
return;
}
const aggMapFromMetrics = metrics.reduce((acc, metric) => {
- const operationField = getLensOperationFromRuleMetric(metric);
+ const { operation, operationWithField, sourceField } = getLensOperationFromRuleMetric(metric);
return {
...acc,
- [metric.name]: operationField,
+ [metric.name]: { operation, operationWithField, sourceField },
};
}, {} as AggMap);
@@ -354,6 +358,26 @@ export function RuleConditionChart({
seriesType: seriesType ? seriesType : 'bar',
};
+ const firstMetricAggMap = aggMap && metrics.length > 0 ? aggMap[metrics[0].name] : undefined;
+ const convertToMaxOperation = ['counter_rate', 'last_value', 'percentile'];
+
+ const orderParams: TopValuesOrderParams = firstMetricAggMap
+ ? {
+ orderDirection: 'desc',
+ orderBy: { type: 'custom' },
+ orderAgg: {
+ label: firstMetricAggMap.operationWithField,
+ dataType: 'number',
+ operationType: convertToMaxOperation.includes(firstMetricAggMap.operation)
+ ? 'max'
+ : firstMetricAggMap.operation,
+ sourceField: firstMetricAggMap.sourceField,
+ isBucketed: false,
+ scale: 'ratio',
+ },
+ }
+ : undefined;
+
if (groupBy && groupBy?.length) {
xYDataLayerOptions.breakdown = {
type: 'top_values',
@@ -362,6 +386,7 @@ export function RuleConditionChart({
size: 3,
secondaryFields: (groupBy as string[]).slice(1),
accuracyMode: false,
+ ...orderParams,
},
};
}
@@ -425,6 +450,7 @@ export function RuleConditionChart({
timeUnit,
seriesType,
warningThresholdReferenceLine,
+ aggMap,
]);
if (