[8.x] [Custom threshold/Metric threshold] [Preview chart] Sort groups by first metric and aggType combination (#199643) (#200534)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Custom threshold/Metric threshold] [Preview chart] Sort groups by
first metric and aggType combination
(#199643)](https://github.com/elastic/kibana/pull/199643)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Bena
Kansara","email":"69037875+benakansara@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-11-18T10:45:35Z","message":"[Custom
threshold/Metric threshold] [Preview chart] Sort groups by first metric
and aggType combination (#199643)\n\nResolves
https://github.com/elastic/kibana/issues/183491\r\n\r\nThis PR sorts the
data in Preview chart of Custom threshold and Metric\r\nthreshold rules
based on first metric and aggType combination used in\r\nthe rule
equation. For `rate`, `percentile` and `last_value`\r\naggregations, I
have used `max` aggregation as those aggregations\r\nrequire additional
params to pass to `LensAttributesBuilder` which are\r\nnot supported
currently. Also, sorting based on equation is not\r\nsupported right
now.\r\n\r\n| Before | After |\r\n| --- | --- |\r\n| <img width=\"601\"
alt=\"Screenshot 2024-11-11 at 17 07
47\"\r\nsrc=\"https://github.com/user-attachments/assets/0f22991f-fa82-4dcf-8f44-7c88d7f85d8e\">\r\n|
<img width=\"596\" alt=\"Screenshot 2024-11-12 at 13 37
06\"\r\nsrc=\"https://github.com/user-attachments/assets/a4a53bac-ecd0-4cc7-9ba5-11c733cc8f88\">\r\n|\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b87e47bdce25b2e560979d0ae532cbb4342ae2e7","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management"],"title":"[Custom
threshold/Metric threshold] [Preview chart] Sort groups by first metric
and aggType
combination","number":199643,"url":"https://github.com/elastic/kibana/pull/199643","mergeCommit":{"message":"[Custom
threshold/Metric threshold] [Preview chart] Sort groups by first metric
and aggType combination (#199643)\n\nResolves
https://github.com/elastic/kibana/issues/183491\r\n\r\nThis PR sorts the
data in Preview chart of Custom threshold and Metric\r\nthreshold rules
based on first metric and aggType combination used in\r\nthe rule
equation. For `rate`, `percentile` and `last_value`\r\naggregations, I
have used `max` aggregation as those aggregations\r\nrequire additional
params to pass to `LensAttributesBuilder` which are\r\nnot supported
currently. Also, sorting based on equation is not\r\nsupported right
now.\r\n\r\n| Before | After |\r\n| --- | --- |\r\n| <img width=\"601\"
alt=\"Screenshot 2024-11-11 at 17 07
47\"\r\nsrc=\"https://github.com/user-attachments/assets/0f22991f-fa82-4dcf-8f44-7c88d7f85d8e\">\r\n|
<img width=\"596\" alt=\"Screenshot 2024-11-12 at 13 37
06\"\r\nsrc=\"https://github.com/user-attachments/assets/a4a53bac-ecd0-4cc7-9ba5-11c733cc8f88\">\r\n|\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b87e47bdce25b2e560979d0ae532cbb4342ae2e7"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199643","number":199643,"mergeCommit":{"message":"[Custom
threshold/Metric threshold] [Preview chart] Sort groups by first metric
and aggType combination (#199643)\n\nResolves
https://github.com/elastic/kibana/issues/183491\r\n\r\nThis PR sorts the
data in Preview chart of Custom threshold and Metric\r\nthreshold rules
based on first metric and aggType combination used in\r\nthe rule
equation. For `rate`, `percentile` and `last_value`\r\naggregations, I
have used `max` aggregation as those aggregations\r\nrequire additional
params to pass to `LensAttributesBuilder` which are\r\nnot supported
currently. Also, sorting based on equation is not\r\nsupported right
now.\r\n\r\n| Before | After |\r\n| --- | --- |\r\n| <img width=\"601\"
alt=\"Screenshot 2024-11-11 at 17 07
47\"\r\nsrc=\"https://github.com/user-attachments/assets/0f22991f-fa82-4dcf-8f44-7c88d7f85d8e\">\r\n|
<img width=\"596\" alt=\"Screenshot 2024-11-12 at 13 37
06\"\r\nsrc=\"https://github.com/user-attachments/assets/a4a53bac-ecd0-4cc7-9ba5-11c733cc8f88\">\r\n|\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b87e47bdce25b2e560979d0ae532cbb4342ae2e7"}}]}]
BACKPORT-->

Co-authored-by: Bena Kansara <69037875+benakansara@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-11-18 23:40:51 +11:00 committed by GitHub
parent 702d5f96c5
commit 307acc756a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 288 additions and 63 deletions

View file

@ -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 = ({

View file

@ -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',
},
],
];

View file

@ -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 =>

View file

@ -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

View file

@ -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;

View file

@ -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<TermsIndexPatternColumn['params'], 'orderDirection' | 'orderBy' | 'orderAgg'>
| 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 (