diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.test.ts b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.test.ts new file mode 100644 index 0000000000000..9ad2f45e358ff --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.test.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { AggType } from '../../../../../../common/custom_threshold_rule/types'; +import { Aggregators } from '../../../../../../common/custom_threshold_rule/types'; +import type { MetricExpression } from '../../../types'; +import { COMPARATORS } from '@kbn/alerting-comparators'; +import { generateChartTitleAndTooltip } from './generate_chart_title_and_tooltip'; + +describe('generateChartTitleAndTooltip', () => { + describe('alerts based on custom threshold rule', () => { + const buildCriterion = (criterion: Partial = {}): MetricExpression => ({ + metrics: [ + { name: 'A', field: 'response_time', aggType: Aggregators.COUNT as unknown as AggType }, + ], + threshold: [100], + comparator: COMPARATORS.GREATER_THAN, + equation: 'A > 100', + ...criterion, + }); + + it('should generate correct title and tooltip when using COUNT aggregation without equation and without filter', () => { + const criterion = buildCriterion({ + metrics: [{ name: 'A', aggType: Aggregators.COUNT as unknown as AggType }], + equation: undefined, + }); + + const { title, tooltip } = generateChartTitleAndTooltip(criterion); + + expect(title).toBe('Equation result for count (all documents)'); + expect(tooltip).toBe('Equation result for count (all documents)'); + }); + + it('should generate correct title and tooltip when using COUNT aggregation with equation and without filter', () => { + const criterion = buildCriterion({ + metrics: [{ name: 'A', aggType: Aggregators.COUNT as unknown as AggType }], + }); + + const { title, tooltip } = generateChartTitleAndTooltip(criterion); + + expect(title).toBe('Equation result for count (all documents) > 100'); + expect(tooltip).toBe('Equation result for count (all documents) > 100'); + }); + + it('should generate correct title and tooltip when using COUNT aggregation with equation and filter', () => { + const criterion = buildCriterion({ + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT as unknown as AggType, + filter: 'status_code:500', + }, + ], + }); + + const { title, tooltip } = generateChartTitleAndTooltip(criterion); + + expect(title).toBe('Equation result for count (status_code:500) > 100'); + expect(tooltip).toBe('Equation result for count (status_code:500) > 100'); + }); + + it('should generate correct title and tooltip when using SUM aggregation with equation and wihout filter', () => { + const criterion = buildCriterion({ + metrics: [{ name: 'A', aggType: Aggregators.SUM as unknown as AggType, field: 'bytes' }], + }); + + const { title, tooltip } = generateChartTitleAndTooltip(criterion); + + expect(title).toBe('Equation result for sum (bytes) > 100'); + expect(tooltip).toBe('Equation result for sum (bytes) > 100'); + }); + + it('should generate correct title and tooltip when using SUM aggregation with equation and filter', () => { + const criterion = buildCriterion({ + metrics: [ + { + name: 'A', + aggType: Aggregators.SUM as unknown as AggType, + field: 'bytes', + filter: 'status_code:200', + }, + ], + }); + + const { title, tooltip } = generateChartTitleAndTooltip(criterion); + + expect(title).toBe('Equation result for sum (bytes, status_code:200) > 100'); + expect(tooltip).toBe('Equation result for sum (bytes, status_code:200) > 100'); + }); + }); +}); diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.ts b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.ts index 8a22b0594be45..8b05b021928d3 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.ts +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/generate_chart_title_and_tooltip.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { CustomThresholdExpressionMetric } from '../../../../../../common/custom_threshold_rule/types'; import type { MetricExpression } from '../../../types'; const CHART_TITLE_LIMIT = 120; @@ -14,6 +15,15 @@ const equationResultText = i18n.translate('xpack.observability.customThreshold.a defaultMessage: 'Equation result for ', }); +const resolveMetricDisplay = (metric: CustomThresholdExpressionMetric) => { + if (metric.field && metric.filter) { + return `${metric.aggType} (${metric.field}, ${metric.filter})`; + } + + return `${metric.aggType} (${ + metric.field ? metric.field : metric.filter ? metric.filter : 'all documents' + })`; +}; export const generateChartTitleAndTooltip = ( criterion: MetricExpression, chartTitleLimit = CHART_TITLE_LIMIT @@ -21,10 +31,7 @@ export const generateChartTitleAndTooltip = ( const metricNameResolver: Record = {}; criterion.metrics.forEach( - (metric) => - (metricNameResolver[metric.name] = `${metric.aggType} (${ - metric.field ? metric.field : metric.filter ? metric.filter : 'all documents' - })`) + (metric) => (metricNameResolver[metric.name] = resolveMetricDisplay(metric)) ); let equation = criterion.equation diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx index 4e972d59b8771..0d737b6d6ce7b 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx @@ -91,9 +91,10 @@ export function MetricRowWithAgg({ name, field: (selectedOptions.length && selectedOptions[0].label) || undefined, aggType, + filter, }); }, - [name, aggType, onChange] + [name, aggType, filter, onChange] ); const handleAggChange = useCallback( @@ -102,9 +103,10 @@ export function MetricRowWithAgg({ name, field: customAggType === Aggregators.COUNT ? undefined : field, aggType: customAggType as Aggregators, + filter, }); }, - [name, field, onChange] + [name, field, filter, onChange] ); const handleFilterChange = useCallback( @@ -113,14 +115,31 @@ export function MetricRowWithAgg({ name, filter: filterString, aggType, + field, }); }, - [name, aggType, onChange] + [name, aggType, field, onChange] ); const isAggInvalid = get(errors, ['metrics', name, 'aggType']) != null; const isFieldInvalid = get(errors, ['metrics', name, 'field']) != null || !field; + const expressionValue = useMemo(() => { + if (aggType === Aggregators.COUNT) { + return filter || DEFAULT_COUNT_FILTER_TITLE; + } + if (field && filter) { + return `${field} (${filter})`; + } + if (field) { + return field; + } + if (filter) { + return filter; + } + return ''; + }, [aggType, field, filter]); + return ( @@ -147,7 +166,7 @@ export function MetricRowWithAgg({ { @@ -201,24 +220,8 @@ export function MetricRowWithAgg({ /> - - {aggType === Aggregators.COUNT ? ( - - - - ) : ( + {aggType !== Aggregators.COUNT && ( + - )} + + )} + + + + diff --git a/x-pack/solutions/observability/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/solutions/observability/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 25e56af391022..a1e2cb032fba9 100644 --- a/x-pack/solutions/observability/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/solutions/observability/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -20,6 +20,16 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const toasts = getService('toasts'); const pageObjects = getPageObjects(['header']); + const closePopover = async () => { + await retry.waitFor('popover to close', async () => { + const isOpen = await testSubjects.exists('o11yClosablePopoverTitleButton', { timeout: 100 }); + if (isOpen) { + await testSubjects.click('o11yClosablePopoverTitleButton'); + } + return !isOpen; + }); + }; + describe('Custom threshold rule', function () { this.tags('includeFirefox'); @@ -109,23 +119,25 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { await find.clickByCssSelector(`option[value="avg"]`); const input1 = await find.byCssSelector('[data-test-subj="aggregationField"] input'); await input1.type('metricset.rtt'); - await testSubjects.click('o11yClosablePopoverTitleButton'); + + await closePopover(); + await retry.waitFor('first aggregation to happen', async () => { const aggregationNameA = await testSubjects.find('aggregationNameA'); return (await aggregationNameA.getVisibleText()) === 'AVERAGE\nmetricset.rtt'; }); - await new Promise((r) => setTimeout(r, 1000)); // set second aggregation await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); await testSubjects.click('aggregationNameB'); await testSubjects.setValue('o11ySearchField', 'service.name : "opbeans-node"'); - await testSubjects.click('o11yClosablePopoverTitleButton'); - await retry.waitFor('first aggregation to happen', async () => { + + await closePopover(); + + await retry.waitFor('second aggregation to happen', async () => { const aggregationNameB = await testSubjects.find('aggregationNameB'); return (await aggregationNameB.getVisibleText()) === 'COUNT\nservice.name : "opbeans-node"'; }); - await new Promise((r) => setTimeout(r, 1000)); }); it('can set custom equation', async () => { @@ -136,12 +148,13 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { ); await customEquationField.click(); await customEquationField.type('A - B'); - await testSubjects.click('o11yClosablePopoverTitleButton'); + + await closePopover(); + await retry.waitFor('custom equation update to happen', async () => { const customEquation = await testSubjects.find('customEquation'); return (await customEquation.getVisibleText()) === 'EQUATION\nA - B'; }); - await new Promise((r) => setTimeout(r, 1000)); }); it('can set threshold', async () => {