Improve reason message of custom threshold rule by adding data view information (#169414)

Resolves #162710

## Summary

This PR improves the custom threshold rule reason message by adding the
data view indices and adjusting the reason for multiple aggregations.
Previously, for multiple aggregations, we repeat some information that
is shared between aggregations, such as interval and group information.

Also, this PR improves the reason messages for single aggregation based
on the selected aggregation and field, similar to what we currently have
in the metric threshold rule.

|Previous reason message | New reason message|
|---|---|

|![image](7a3d9778-f84b-4bbb-a8e0-a99debfe78d1)|

## 🧪 How to test
- Create some custom threshold rules and check the reason message
    - Single condition (different aggregators and comparators)
        - With a label for the equation
        - Without a label
    - Multiple conditions (different aggregators and comparators)
        - With a label for the equation
        - Without a label


### Known issue
I created an issue for `is not between` comparator and I wasn't able to
genarate an alert for it:
https://github.com/elastic/kibana/issues/169524
This commit is contained in:
Maryam Saeidi 2023-10-26 13:22:05 +02:00 committed by GitHub
parent d94283363e
commit c15da16407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 364 additions and 240 deletions

View file

@ -17,10 +17,9 @@ import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server';
import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks';
import {
createMetricThresholdExecutor,
FIRED_ACTIONS,
MetricThresholdAlertContext,
NO_DATA_ACTIONS,
} from './custom_threshold_executor';
import { FIRED_ACTIONS, NO_DATA_ACTIONS } from './translations';
import { Evaluation } from './lib/evaluate_rule';
import type { LogMeta, Logger } from '@kbn/logging';
import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
@ -237,10 +236,9 @@ describe('The metric threshold alert type', () => {
await execute(Comparator.GT, [0.75]);
const { action } = mostRecentAction(instanceID);
expect(action.group).toBeUndefined();
expect(action.reason).toContain('is 1');
expect(action.reason).toContain('Alert when > 0.75');
expect(action.reason).toContain('test.metric.1');
expect(action.reason).toContain('in the last 1 min');
expect(action.reason).toBe(
'test.metric.1 is 1, above the threshold of 0.75. (duration: 1 min, data view: mockedIndexPattern)'
);
});
});
@ -997,16 +995,10 @@ describe('The metric threshold alert type', () => {
const instanceID = '*';
await execute(Comparator.GT_OR_EQ, [1.0], [3.0]);
const { action } = mostRecentAction(instanceID);
const reasons = action.reason.split('\n');
expect(reasons.length).toBe(2);
expect(reasons[0]).toContain('test.metric.1');
expect(reasons[1]).toContain('test.metric.2');
expect(reasons[0]).toContain('is 1');
expect(reasons[1]).toContain('is 3');
expect(reasons[0]).toContain('Alert when >= 1');
expect(reasons[1]).toContain('Alert when >= 3');
expect(reasons[0]).toContain('in the last 1 min');
expect(reasons[1]).toContain('in the last 1 min');
const reasons = action.reason;
expect(reasons).toBe(
'test.metric.1 is 1, above the threshold of 1; test.metric.2 is 3, above the threshold of 3. (duration: 1 min, data view: mockedIndexPattern)'
);
});
});
describe('querying with the count aggregator', () => {

View file

@ -7,7 +7,6 @@
import { isEqual } from 'lodash';
import { TypeOf } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import {
ALERT_ACTION_GROUP,
ALERT_EVALUATION_VALUES,
@ -23,11 +22,10 @@ import {
import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server';
import { IBasePath, Logger } from '@kbn/core/server';
import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server';
import { AlertsLocatorParams, getAlertUrl, TimeUnitChar } from '../../../../common';
import { createFormatter } from '../../../../common/custom_threshold_rule/formatters';
import { Comparator } from '../../../../common/custom_threshold_rule/types';
import { AlertsLocatorParams, getAlertUrl } from '../../../../common';
import { ObservabilityConfig } from '../../..';
import { AlertStates, searchConfigurationSchema } from './types';
import { FIRED_ACTIONS, NO_DATA_ACTIONS } from './translations';
import {
buildFiredAlertReason,
@ -45,6 +43,7 @@ import {
getFormattedGroupBy,
} from './utils';
import { formatAlertResult } from './lib/format_alert_result';
import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule';
import { MissingGroupsRecord } from './lib/check_missing_group';
import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record';
@ -64,7 +63,8 @@ export interface MetricThresholdAlertContext extends Record<string, unknown> {
group?: object;
reason?: string;
timestamp: string; // ISO string
value?: Array<number | null> | null;
// String type is for [NO DATA]
value?: Array<number | string | null>;
}
export const FIRED_ACTIONS_ID = 'custom_threshold.fired';
@ -240,14 +240,7 @@ export const createMetricThresholdExecutor = ({
let reason;
if (nextState === AlertStates.ALERT) {
reason = alertResults
.map((result) =>
buildFiredAlertReason({
...formatAlertResult(result[group]),
group,
})
)
.join('\n');
reason = buildFiredAlertReason(alertResults, group, dataView);
}
/* NO DATA STATE HANDLING
@ -383,61 +376,3 @@ export const createMetricThresholdExecutor = ({
},
};
};
export const FIRED_ACTIONS = {
id: 'custom_threshold.fired',
name: i18n.translate('xpack.observability.customThreshold.rule.alerting.custom_threshold.fired', {
defaultMessage: 'Alert',
}),
};
export const NO_DATA_ACTIONS = {
id: 'custom_threshold.nodata',
name: i18n.translate(
'xpack.observability.customThreshold.rule.alerting.custom_threshold.nodata',
{
defaultMessage: 'No Data',
}
),
};
const formatAlertResult = <AlertResult>(
alertResult: {
metric: string;
currentValue: number | null;
threshold: number[];
comparator: Comparator;
timeSize: number;
timeUnit: TimeUnitChar;
} & AlertResult
) => {
const { metric, currentValue, threshold, comparator } = alertResult;
const noDataValue = i18n.translate(
'xpack.observability.customThreshold.rule.alerting.threshold.noDataFormattedValue',
{ defaultMessage: '[NO DATA]' }
);
if (metric.endsWith('.pct')) {
const formatter = createFormatter('percent');
return {
...alertResult,
currentValue:
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(threshold)
? threshold.map((v: number) => formatter(v))
: formatter(threshold),
comparator,
};
}
const formatter = createFormatter('highPrecision');
return {
...alertResult,
currentValue:
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(threshold)
? threshold.map((v: number) => formatter(v))
: formatter(threshold),
comparator,
};
};

View file

@ -8,14 +8,26 @@
import moment from 'moment';
import { ElasticsearchClient } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types';
import { isCustom } from './metric_expression_params';
import {
Aggregators,
CustomMetricExpressionParams,
MetricExpressionParams,
} from '../../../../../common/custom_threshold_rule/types';
import { AdditionalContext, getIntervalInSeconds } from '../utils';
import { SearchConfigurationType } from '../custom_threshold_executor';
import { CUSTOM_EQUATION_I18N, DOCUMENT_COUNT_I18N } from '../messages';
import {
AVERAGE_I18N,
CARDINALITY_I18N,
CUSTOM_EQUATION_I18N,
DOCUMENT_COUNT_I18N,
MAX_I18N,
MIN_I18N,
SUM_I18N,
} from '../translations';
import { createTimerange } from './create_timerange';
import { getData } from './get_data';
import { checkMissingGroups, MissingGroupsRecord } from './check_missing_group';
import { isCustom } from './metric_expression_params';
export interface EvaluatedRuleParams {
criteria: MetricExpressionParams[];
@ -33,6 +45,26 @@ export type Evaluation = Omit<MetricExpressionParams, 'metric'> & {
context?: AdditionalContext;
};
const getMetric = (criterion: CustomMetricExpressionParams) => {
if (!criterion.label && criterion.metrics.length === 1) {
switch (criterion.metrics[0].aggType) {
case Aggregators.COUNT:
return DOCUMENT_COUNT_I18N;
case Aggregators.AVERAGE:
return AVERAGE_I18N(criterion.metrics[0].field!);
case Aggregators.MAX:
return MAX_I18N(criterion.metrics[0].field!);
case Aggregators.MIN:
return MIN_I18N(criterion.metrics[0].field!);
case Aggregators.CARDINALITY:
return CARDINALITY_I18N(criterion.metrics[0].field!);
case Aggregators.SUM:
return SUM_I18N(criterion.metrics[0].field!);
}
}
return criterion.label || CUSTOM_EQUATION_I18N;
};
export const evaluateRule = async <Params extends EvaluatedRuleParams = EvaluatedRuleParams>(
esClient: ElasticsearchClient,
params: Params,
@ -104,10 +136,8 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate
metric:
criterion.aggType === 'count'
? DOCUMENT_COUNT_I18N
: isCustom(criterion) && criterion.label
? criterion.label
: criterion.aggType === 'custom'
? CUSTOM_EQUATION_I18N
: isCustom(criterion)
? getMetric(criterion)
: criterion.metric,
currentValue: result.value,
timestamp: moment(calculatedTimerange.end).toISOString(),

View file

@ -0,0 +1,47 @@
/*
* 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 { createFormatter } from '../../../../../common/custom_threshold_rule/formatters';
import { Evaluation } from './evaluate_rule';
export type FormattedEvaluation = Omit<Evaluation, 'currentValue' | 'threshold'> & {
currentValue: string;
threshold: string[];
};
export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvaluation => {
const { metric, currentValue, threshold, comparator } = evaluationResult;
const noDataValue = i18n.translate(
'xpack.observability.customThreshold.rule.alerting.threshold.noDataFormattedValue',
{ defaultMessage: '[NO DATA]' }
);
if (metric.endsWith('.pct')) {
const formatter = createFormatter('percent');
return {
...evaluationResult,
currentValue:
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(threshold)
? threshold.map((v: number) => formatter(v))
: [formatter(threshold)],
comparator,
};
}
const formatter = createFormatter('highPrecision');
return {
...evaluationResult,
currentValue:
currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue,
threshold: Array.isArray(threshold)
? threshold.map((v: number) => formatter(v))
: [formatter(threshold)],
comparator,
};
};

View file

@ -7,50 +7,40 @@
import { i18n } from '@kbn/i18n';
import { Comparator } from '../../../../common/custom_threshold_rule/types';
import { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../../common';
import { formatDurationFromTimeUnitChar } from '../../../../common';
import { Evaluation } from './lib/evaluate_rule';
import { formatAlertResult, FormattedEvaluation } from './lib/format_alert_result';
import { UNGROUPED_FACTORY_KEY } from './utils';
export const DOCUMENT_COUNT_I18N = i18n.translate(
'xpack.observability.customThreshold.rule.threshold.documentCount',
{
defaultMessage: 'Document count',
}
);
export const CUSTOM_EQUATION_I18N = i18n.translate(
'xpack.observability.customThreshold.rule.threshold.customEquation',
{
defaultMessage: 'Custom equation',
}
);
const toNumber = (value: number | string) =>
typeof value === 'string' ? parseFloat(value) : value;
const belowText = i18n.translate('xpack.observability.customThreshold.rule.threshold.below', {
defaultMessage: 'below',
});
const aboveText = i18n.translate('xpack.observability.customThreshold.rule.threshold.above', {
defaultMessage: 'above',
});
const betweenText = i18n.translate('xpack.observability.customThreshold.rule.threshold.between', {
defaultMessage: 'between',
});
const notBetweenText = i18n.translate(
'xpack.observability.customThreshold.rule.threshold.notBetween',
{
defaultMessage: 'not between',
}
);
const recoveredComparatorToI18n = (
comparator: Comparator,
threshold: number[],
currentValue: number
) => {
const belowText = i18n.translate(
'xpack.observability.customThreshold.rule.threshold.belowRecovery',
{
defaultMessage: 'below',
}
);
const aboveText = i18n.translate(
'xpack.observability.customThreshold.rule.threshold.aboveRecovery',
{
defaultMessage: 'above',
}
);
switch (comparator) {
case Comparator.BETWEEN:
return currentValue < threshold[0] ? belowText : aboveText;
case Comparator.OUTSIDE_RANGE:
return i18n.translate('xpack.observability.customThreshold.rule.threshold.betweenRecovery', {
defaultMessage: 'between',
});
return betweenText;
case Comparator.GT:
case Comparator.GT_OR_EQ:
return belowText;
@ -60,6 +50,21 @@ const recoveredComparatorToI18n = (
}
};
const alertComparatorToI18n = (comparator: Comparator) => {
switch (comparator) {
case Comparator.BETWEEN:
return betweenText;
case Comparator.OUTSIDE_RANGE:
return notBetweenText;
case Comparator.GT:
case Comparator.GT_OR_EQ:
return aboveText;
case Comparator.LT:
case Comparator.LT_OR_EQ:
return belowText;
}
};
const thresholdToI18n = ([a, b]: Array<number | string>) => {
if (typeof b === 'undefined') return a;
return i18n.translate('xpack.observability.customThreshold.rule.threshold.thresholdRange', {
@ -70,25 +75,63 @@ const thresholdToI18n = ([a, b]: Array<number | string>) => {
const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? '' : ` for ${group}`);
export const buildFiredAlertReason: (alertResult: {
group: string;
metric: string;
comparator: Comparator;
threshold: Array<number | string>;
currentValue: number | string;
timeSize: number;
timeUnit: TimeUnitChar;
}) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) =>
export const buildFiredAlertReason: (
alertResults: Array<Record<string, Evaluation>>,
group: string,
dataView: string
) => string = (alertResults, group, dataView) => {
const aggregationReason =
alertResults
.map((result: any) => buildAggregationReason(formatAlertResult(result[group])))
.join('; ') + '.';
const sharedReason =
'(' +
[
i18n.translate('xpack.observability.customThreshold.rule.reason.forTheLast', {
defaultMessage: 'duration: {duration}',
values: {
duration: formatDurationFromTimeUnitChar(
alertResults[0][group].timeSize,
alertResults[0][group].timeUnit
),
},
}),
i18n.translate('xpack.observability.customThreshold.rule.reason.dataView', {
defaultMessage: 'data view: {dataView}',
values: {
dataView,
},
}),
group !== UNGROUPED_FACTORY_KEY
? i18n.translate('xpack.observability.customThreshold.rule.reason.group', {
defaultMessage: 'group: {group}',
values: {
group,
},
})
: null,
]
.filter((item) => !!item)
.join(', ') +
')';
return aggregationReason + ' ' + sharedReason;
};
const buildAggregationReason: (evaluation: FormattedEvaluation) => string = ({
metric,
comparator,
threshold,
currentValue,
timeSize,
timeUnit,
}) =>
i18n.translate('xpack.observability.customThreshold.rule.threshold.firedAlertReason', {
defaultMessage:
'{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.',
defaultMessage: '{metric} is {currentValue}, {comparator} the threshold of {threshold}',
values: {
group: formatGroup(group),
metric,
comparator,
comparator: alertComparatorToI18n(comparator),
threshold: thresholdToI18n(threshold),
currentValue,
duration: formatDurationFromTimeUnitChar(timeSize, timeUnit),
},
});
@ -138,88 +181,3 @@ export const buildErrorAlertReason = (metric: string) =>
metric,
},
});
export const groupByKeysActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.groupByKeysActionVariableDescription',
{
defaultMessage: 'The object containing groups that are reporting data',
}
);
export const alertDetailUrlActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription',
{
defaultMessage:
'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.',
}
);
export const reasonActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.reasonActionVariableDescription',
{
defaultMessage: 'A concise description of the reason for the alert',
}
);
export const timestampActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.timestampDescription',
{
defaultMessage: 'A timestamp of when the alert was detected.',
}
);
export const valueActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.valueActionVariableDescription',
{
defaultMessage: 'List of the condition values.',
}
);
export const viewInAppUrlActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.viewInAppUrlActionVariableDescription',
{
defaultMessage: 'Link to the alert source',
}
);
export const cloudActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.cloudActionVariableDescription',
{
defaultMessage: 'The cloud object defined by ECS if available in the source.',
}
);
export const hostActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.hostActionVariableDescription',
{
defaultMessage: 'The host object defined by ECS if available in the source.',
}
);
export const containerActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.containerActionVariableDescription',
{
defaultMessage: 'The container object defined by ECS if available in the source.',
}
);
export const orchestratorActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.orchestratorActionVariableDescription',
{
defaultMessage: 'The orchestrator object defined by ECS if available in the source.',
}
);
export const labelsActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.labelsActionVariableDescription',
{
defaultMessage: 'List of labels associated with the entity where this alert triggered.',
}
);
export const tagsActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.tagsActionVariableDescription',
{
defaultMessage: 'List of tags associated with the entity where this alert triggered.',
}
);

View file

@ -38,13 +38,10 @@ import {
tagsActionVariableDescription,
timestampActionVariableDescription,
valueActionVariableDescription,
} from './messages';
} from './translations';
import { oneOfLiterals, validateKQLStringFilter } from './utils';
import {
createMetricThresholdExecutor,
FIRED_ACTIONS,
NO_DATA_ACTIONS,
} from './custom_threshold_executor';
import { createMetricThresholdExecutor } from './custom_threshold_executor';
import { FIRED_ACTIONS, NO_DATA_ACTIONS } from './translations';
import { ObservabilityConfig } from '../../..';
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/custom_threshold_rule/constants';

View file

@ -0,0 +1,164 @@
/*
* 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';
export const FIRED_ACTIONS = {
id: 'custom_threshold.fired',
name: i18n.translate('xpack.observability.customThreshold.rule.alerting.custom_threshold.fired', {
defaultMessage: 'Alert',
}),
};
export const NO_DATA_ACTIONS = {
id: 'custom_threshold.nodata',
name: i18n.translate(
'xpack.observability.customThreshold.rule.alerting.custom_threshold.nodata',
{
defaultMessage: 'No Data',
}
),
};
export const DOCUMENT_COUNT_I18N = i18n.translate(
'xpack.observability.customThreshold.rule.aggregators.documentCount',
{
defaultMessage: 'Document count',
}
);
export const AVERAGE_I18N = (metric: string) =>
i18n.translate('xpack.observability.customThreshold.rule.aggregators.average', {
defaultMessage: 'Average {metric}',
values: {
metric,
},
});
export const MAX_I18N = (metric: string) =>
i18n.translate('xpack.observability.customThreshold.rule.aggregators.max', {
defaultMessage: 'Max {metric}',
values: {
metric,
},
});
export const MIN_I18N = (metric: string) =>
i18n.translate('xpack.observability.customThreshold.rule.aggregators.min', {
defaultMessage: 'Min {metric}',
values: {
metric,
},
});
export const CARDINALITY_I18N = (metric: string) =>
i18n.translate('xpack.observability.customThreshold.rule.aggregators.cardinality', {
defaultMessage: 'Cardinality of the {metric}',
values: {
metric,
},
});
export const SUM_I18N = (metric: string) =>
i18n.translate('xpack.observability.customThreshold.rule.aggregators.sum', {
defaultMessage: 'Sum of the {metric}',
values: {
metric,
},
});
export const CUSTOM_EQUATION_I18N = i18n.translate(
'xpack.observability.customThreshold.rule.aggregators.customEquation',
{
defaultMessage: 'Custom equation',
}
);
export const groupByKeysActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.groupByKeysActionVariableDescription',
{
defaultMessage: 'The object containing groups that are reporting data',
}
);
export const alertDetailUrlActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription',
{
defaultMessage:
'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.',
}
);
export const reasonActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.reasonActionVariableDescription',
{
defaultMessage: 'A concise description of the reason for the alert',
}
);
export const timestampActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.timestampDescription',
{
defaultMessage: 'A timestamp of when the alert was detected.',
}
);
export const valueActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.valueActionVariableDescription',
{
defaultMessage: 'List of the condition values.',
}
);
export const viewInAppUrlActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.viewInAppUrlActionVariableDescription',
{
defaultMessage: 'Link to the alert source',
}
);
export const cloudActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.cloudActionVariableDescription',
{
defaultMessage: 'The cloud object defined by ECS if available in the source.',
}
);
export const hostActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.hostActionVariableDescription',
{
defaultMessage: 'The host object defined by ECS if available in the source.',
}
);
export const containerActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.containerActionVariableDescription',
{
defaultMessage: 'The container object defined by ECS if available in the source.',
}
);
export const orchestratorActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.orchestratorActionVariableDescription',
{
defaultMessage: 'The orchestrator object defined by ECS if available in the source.',
}
);
export const labelsActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.labelsActionVariableDescription',
{
defaultMessage: 'List of labels associated with the entity where this alert triggered.',
}
);
export const tagsActionVariableDescription = i18n.translate(
'xpack.observability.customThreshold.rule.tagsActionVariableDescription',
{
defaultMessage: 'List of tags associated with the entity where this alert triggered.',
}
);

View file

@ -213,9 +213,9 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 2.5 in the last 5 mins. Alert when > 0.5.'
`Average system.cpu.user.pct is 250%, above the threshold of 50%. (duration: 5 mins, data view: ${DATE_VIEW})`
);
expect(resp.hits.hits[0]._source?.value).eql('2.5');
expect(resp.hits.hits[0]._source?.value).eql('250%');
});
});
});

View file

@ -208,7 +208,7 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation reported no data in the last 5m'
'Average system.cpu.user.pct reported no data in the last 5m'
);
expect(resp.hits.hits[0]._source?.value).eql('[NO DATA]');
});

View file

@ -40,6 +40,7 @@ export default function ({ getService }: FtrProviderContext) {
describe('Custom Threshold rule - AVG - US - FIRED', () => {
const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
const ALERT_ACTION_INDEX = 'alert-action-threshold';
const DATE_VIEW = 'traces-apm*,metrics-apm*,logs-apm*';
const DATA_VIEW_ID = 'data-view-id';
let synthtraceEsClient: ApmSynthtraceEsClient;
@ -55,7 +56,7 @@ export default function ({ getService }: FtrProviderContext) {
supertest,
name: 'test-data-view',
id: DATA_VIEW_ID,
title: 'traces-apm*,metrics-apm*,logs-apm*',
title: DATE_VIEW,
});
});
@ -217,7 +218,7 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 10,000,000 in the last 5 mins. Alert when > 7,500,000.'
`Average span.self_time.sum.us is 10,000,000, above the threshold of 7,500,000. (duration: 5 mins, data view: ${DATE_VIEW})`
);
expect(resp.hits.hits[0]._source?.value).eql('10,000,000');
});

View file

@ -224,7 +224,7 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 1 in the last 1 min. Alert when > 0.9.'
`Custom equation is 1, above the threshold of 0.9. (duration: 1 min, data view: ${DATE_VIEW})`
);
expect(resp.hits.hits[0]._source?.value).eql('1');
});

View file

@ -212,7 +212,7 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 3 in the last 1 min. Alert when > 2.'
`Document count is 3, above the threshold of 2. (duration: 1 min, data view: ${DATE_VIEW})`
);
expect(resp.hits.hits[0]._source?.value).eql('3');
});

View file

@ -240,9 +240,9 @@ export default function ({ getService }: FtrProviderContext) {
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 0.8 in the last 1 min for host-0,container-0. Alert when >= 0.2.'
`Average system.cpu.total.norm.pct is 80%, above the threshold of 20%. (duration: 1 min, data view: ${DATE_VIEW}, group: host-0,container-0)`
);
expect(resp.hits.hits[0]._source?.value).eql('0.8');
expect(resp.hits.hits[0]._source?.value).eql('80%');
expect(resp.hits.hits[0]._source?.host).eql(
'{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}'
);

View file

@ -232,9 +232,9 @@ export default function ({ getService }: FtrProviderContext) {
`${protocol}s://${hostname}:${port}/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.'
`Average system.cpu.total.norm.pct is 80%, above the threshold of 20%. (duration: 1 min, data view: ${DATE_VIEW}, group: host-0)`
);
expect(resp.hits.hits[0]._source?.value).eql('0.8');
expect(resp.hits.hits[0]._source?.value).eql('80%');
expect(resp.hits.hits[0]._source?.host).eql(
'{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}'
);