[AO] Add evaluation values for metric threshold and inventory rules (#154255)

Closes #153877

## Summary

This PR adds a new field called `kibana.alert.evaluation.values` to the
alert document for metric threshold and inventory rules. This is an
array of numbers but depending on the result of the rule execution, the
value might be `null` too.


![image](https://user-images.githubusercontent.com/12370520/230380396-fcfa10d8-a119-497b-bd94-9f567ecb8fc5.png)

We want to use this result in the metric threshold alert details page,
so I checked whether this value can be retrieved correctly there or not:

![image](https://user-images.githubusercontent.com/12370520/230380867-3a0520fd-687c-4d88-8161-278abfe8fc88.png)

**Note**
I will add tests later, I would like to get feedback about the
implementation first.

## 🧪 How to test
- Add xpack.observability.unsafe.alertDetails.metrics.enabled: true to
the Kibana config
- Create a metric threshold and inventory rule that generates an alert
- Check the alert document for the `kibana.alert.evaluation.values`
field, it should be an array with the result of evaluation for the
related criteria
- If you are using metricbeat, stop it so the value of evaluation will
be null
- Go to the alert details page, you should be able to see the main chart
even when the evaluation value is null
- Check the alert document for the `kibana.alert.evaluation.values`
field, it should be an array including a null value
This commit is contained in:
Maryam Saeidi 2023-04-20 15:05:45 +02:00 committed by GitHub
parent fd26017162
commit 11721308fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 14 deletions

View file

@ -6,7 +6,11 @@
* Side Public License, v 1.
*/
import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils';
import {
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_EVALUATION_VALUES,
} from '@kbn/rule-data-utils';
export const legacyExperimentalFieldMap = {
[ALERT_EVALUATION_THRESHOLD]: {
@ -15,6 +19,12 @@ export const legacyExperimentalFieldMap = {
required: false,
},
[ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100, required: false },
[ALERT_EVALUATION_VALUES]: {
type: 'scaled_float',
scaling_factor: 100,
required: false,
array: true,
},
} as const;
export type ExperimentalRuleFieldMap = typeof legacyExperimentalFieldMap;

View file

@ -85,6 +85,7 @@ const EVENT_MODULE = 'event.module' as const;
const ALERT_BUILDING_BLOCK_TYPE = `${ALERT_NAMESPACE}.building_block_type` as const;
const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const;
const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const;
const ALERT_EVALUATION_VALUES = `${ALERT_NAMESPACE}.evaluation.values` as const;
// Fields pertaining to the rule associated with the alert
const ALERT_RULE_EXCEPTIONS_LIST = `${ALERT_RULE_NAMESPACE}.exceptions_list` as const;
@ -125,6 +126,7 @@ const fields = {
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_EVALUATION_VALUES,
ALERT_FLAPPING,
ALERT_MAINTENANCE_WINDOW_IDS,
ALERT_INSTANCE_ID,
@ -192,6 +194,7 @@ export {
ALERT_BUILDING_BLOCK_TYPE,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_EVALUATION_VALUES,
ALERT_RULE_EXCEPTIONS_LIST,
ALERT_RULE_NAMESPACE_FIELD,
ALERT_THREAT_FRAMEWORK,

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { ALERT_REASON, ALERT_ACTION_GROUP } from '@kbn/rule-data-utils';
import { ALERT_REASON, ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES } from '@kbn/rule-data-utils';
import { first, get } from 'lodash';
import {
ActionGroup,
@ -65,8 +65,7 @@ type InventoryMetricThresholdAlertFactory = (
reason: string,
actionGroup: InventoryThrehsoldActionGroup,
additionalContext?: AdditionalContext | null,
threshold?: number | undefined,
value?: number | undefined
evaluationValues?: Array<number | null>
) => InventoryMetricThresholdAlert;
export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =>
@ -109,13 +108,15 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
id,
reason,
actionGroup,
additionalContext
additionalContext,
evaluationValues
) =>
alertWithLifecycle({
id,
fields: {
[ALERT_REASON]: reason,
[ALERT_ACTION_GROUP]: actionGroup,
[ALERT_EVALUATION_VALUES]: evaluationValues,
...flattenAdditionalContext(additionalContext),
},
});
@ -243,7 +244,18 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
new Set([...(additionalContext.tags ?? []), ...ruleTags])
);
const alert = alertFactory(group, reason, actionGroupId, additionalContext);
const evaluationValues = results.reduce((acc: Array<number | null>, result) => {
acc.push(result[group].currentValue);
return acc;
}, []);
const alert = alertFactory(
group,
reason,
actionGroupId,
additionalContext,
evaluationValues
);
const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString();
const alertUuid = getAlertUuid(group);

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { ALERT_ACTION_GROUP, ALERT_REASON } from '@kbn/rule-data-utils';
import { ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, ALERT_REASON } from '@kbn/rule-data-utils';
import { isEqual } from 'lodash';
import {
ActionGroupIdsOf,
@ -51,8 +51,8 @@ export type MetricThresholdRuleTypeState = RuleTypeState & {
groupBy?: string | string[];
filterQuery?: string;
};
export type MetricThresholdAlertState = AlertState; // no specific instace state used
export type MetricThresholdAlertContext = AlertContext; // no specific instace state used
export type MetricThresholdAlertState = AlertState; // no specific instance state used
export type MetricThresholdAlertContext = AlertContext; // no specific instance state used
export const FIRED_ACTIONS_ID = 'metrics.threshold.fired';
export const WARNING_ACTIONS_ID = 'metrics.threshold.warning';
@ -79,8 +79,7 @@ type MetricThresholdAlertFactory = (
reason: string,
actionGroup: MetricThresholdActionGroup,
additionalContext?: AdditionalContext | null,
threshold?: number | undefined,
value?: number | undefined
evaluationValues?: Array<number | null>
) => MetricThresholdAlert;
export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
@ -117,13 +116,15 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
id,
reason,
actionGroup,
additionalContext
additionalContext,
evaluationValues
) =>
alertWithLifecycle({
id,
fields: {
[ALERT_REASON]: reason,
[ALERT_ACTION_GROUP]: actionGroup,
[ALERT_EVALUATION_VALUES]: evaluationValues,
...flattenAdditionalContext(additionalContext),
},
});
@ -295,7 +296,18 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
new Set([...(additionalContext.tags ?? []), ...options.rule.tags])
);
const alert = alertFactory(`${group}`, reason, actionGroupId, additionalContext);
const evaluationValues = alertResults.reduce((acc: Array<number | null>, result) => {
acc.push(result[group].currentValue);
return acc;
}, []);
const alert = alertFactory(
`${group}`,
reason,
actionGroupId,
additionalContext,
evaluationValues
);
const alertUuid = getAlertUuid(group);
scheduledActionsCount++;

View file

@ -77,7 +77,7 @@ type CastSingle<T extends t.Type<any>> = t.Type<
>;
const createCastArrayRt = <T extends t.Type<any>>(type: T): CastArray<T> => {
const union = t.union([type, t.array(type)]);
const union = t.union([type, t.array(t.union([type, t.nullType]))]);
return new t.Type('castArray', union.is, union.validate, (a) => (Array.isArray(a) ? a : [a]));
};