[Log threhsod, SLO burn rate] Save the ECS group by fields at the AAD root level (#189260)

Related to #183220

## Summary

This PR saves ECS groups in the Alert As Data (AAD) document for the log
threshold and SLO burn rate rules.

|Rule|AAD document|
|---|---|
|SLO burn
rate|![image](https://github.com/user-attachments/assets/c5476e33-95d0-4c39-af12-2ef5a9768ab0)|
|Log
threshold|![image](https://github.com/user-attachments/assets/34fc6662-c4c3-4b3e-9d77-f0959f726394)|

### 🧪 How to test
- Create a log threshold and SLO burn rate rule with multiple groups
(both ECS and non-ECS fields)
- Check the related AAD document; you should be able to see the ECS
fields at the root level and not see non-ECS fields there
- Check the same information for the recovered alerts
- Rules without group by should work as before

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maryam Saeidi 2024-07-26 17:12:29 +02:00 committed by GitHub
parent 9c242ac773
commit b109e75e64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 17 deletions

View file

@ -31,7 +31,7 @@ import {
PublicAlertsClient,
RecoveredAlertData,
} from '@kbn/alerting-plugin/server/alerts_client/types';
import { type Group } from '@kbn/observability-alerting-rule-utils';
import { getEcsGroups, type Group } from '@kbn/observability-alerting-rule-utils';
import { ecsFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map';
import { decodeOrThrow } from '@kbn/io-ts-utils';
@ -191,6 +191,7 @@ export const createLogThresholdExecutor =
[ALERT_CONTEXT]: alertContext,
[ALERT_GROUP]: groups,
...flattenAdditionalContext(rootLevelContext),
...getEcsGroups(groups),
};
alertsClient.setAlertData({

View file

@ -338,19 +338,22 @@ describe('BurnRateRuleExecutor', () => {
});
it('schedules an alert when both windows of first window definition burn rate have reached the threshold', async () => {
const slo = createSLO({ objective: { target: 0.9 }, groupBy: ['group.by.field'] });
const slo = createSLO({
objective: { target: 0.9 },
groupBy: ['group.by.field', 'client.geo.continent_name'],
});
const ruleParams = someRuleParamsWithWindows({ sloId: slo.id });
soClientMock.find.mockResolvedValueOnce(createFindResponse([slo]));
const buckets = [
{
instanceId: 'foo',
instanceId: 'foo,asia',
windows: [
{ shortWindowBurnRate: 2.1, longWindowBurnRate: 2.3 },
{ shortWindowBurnRate: 0.9, longWindowBurnRate: 1.2 },
],
},
{
instanceId: 'bar',
instanceId: 'bar,asia',
windows: [
{ shortWindowBurnRate: 2.2, longWindowBurnRate: 2.5 },
{ shortWindowBurnRate: 0.9, longWindowBurnRate: 1.2 },
@ -391,75 +394,93 @@ describe('BurnRateRuleExecutor', () => {
});
expect(servicesMock.alertsClient?.report).toBeCalledWith({
id: 'foo',
id: 'foo,asia',
actionGroup: ALERT_ACTION.id,
state: {
alertState: AlertStates.ALERT,
},
payload: {
[ALERT_REASON]:
'CRITICAL: The burn rate for the past 1h is 2.3 and for the past 5m is 2.1 for foo. Alert when above 2 for both windows',
'CRITICAL: The burn rate for the past 1h is 2.3 and for the past 5m is 2.1 for foo,asia. Alert when above 2 for both windows',
[ALERT_EVALUATION_THRESHOLD]: 2,
[ALERT_EVALUATION_VALUE]: 2.1,
[SLO_ID_FIELD]: slo.id,
[SLO_REVISION_FIELD]: slo.revision,
[SLO_INSTANCE_ID_FIELD]: 'foo',
[SLO_INSTANCE_ID_FIELD]: 'foo,asia',
[ALERT_GROUP]: [
{
field: 'group.by.field',
value: 'foo',
},
{
field: 'client.geo.continent_name',
value: 'asia',
},
],
'client.geo.continent_name': 'asia',
},
});
expect(servicesMock.alertsClient?.report).toBeCalledWith({
id: 'bar',
id: 'bar,asia',
actionGroup: ALERT_ACTION.id,
state: {
alertState: AlertStates.ALERT,
},
payload: {
[ALERT_REASON]:
'CRITICAL: The burn rate for the past 1h is 2.5 and for the past 5m is 2.2 for bar. Alert when above 2 for both windows',
'CRITICAL: The burn rate for the past 1h is 2.5 and for the past 5m is 2.2 for bar,asia. Alert when above 2 for both windows',
[ALERT_EVALUATION_THRESHOLD]: 2,
[ALERT_EVALUATION_VALUE]: 2.2,
[SLO_ID_FIELD]: slo.id,
[SLO_REVISION_FIELD]: slo.revision,
[SLO_INSTANCE_ID_FIELD]: 'bar',
[SLO_INSTANCE_ID_FIELD]: 'bar,asia',
[ALERT_GROUP]: [
{
field: 'group.by.field',
value: 'bar',
},
{
field: 'client.geo.continent_name',
value: 'asia',
},
],
'client.geo.continent_name': 'asia',
},
});
expect(servicesMock.alertsClient?.setAlertData).toHaveBeenCalledTimes(2);
expect(servicesMock.alertsClient?.setAlertData).toHaveBeenNthCalledWith(1, {
id: 'foo',
id: 'foo,asia',
context: expect.objectContaining({
longWindow: { burnRate: 2.3, duration: '1h' },
shortWindow: { burnRate: 2.1, duration: '5m' },
burnRateThreshold: 2,
reason:
'CRITICAL: The burn rate for the past 1h is 2.3 and for the past 5m is 2.1 for foo. Alert when above 2 for both windows',
'CRITICAL: The burn rate for the past 1h is 2.3 and for the past 5m is 2.1 for foo,asia. Alert when above 2 for both windows',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
}),
});
expect(servicesMock.alertsClient?.setAlertData).toHaveBeenNthCalledWith(2, {
id: 'bar',
id: 'bar,asia',
context: expect.objectContaining({
longWindow: { burnRate: 2.5, duration: '1h' },
shortWindow: { burnRate: 2.2, duration: '5m' },
burnRateThreshold: 2,
reason:
'CRITICAL: The burn rate for the past 1h is 2.5 and for the past 5m is 2.2 for bar. Alert when above 2 for both windows',
'CRITICAL: The burn rate for the past 1h is 2.5 and for the past 5m is 2.2 for bar,asia. Alert when above 2 for both windows',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
}),
});
expect(alertsLocatorMock.getLocation).toBeCalledWith({
expect(alertsLocatorMock.getLocation).toHaveBeenCalledTimes(2);
expect(alertsLocatorMock.getLocation).toHaveBeenNthCalledWith(1, {
baseUrl: 'https://kibana.dev',
kuery: 'kibana.alert.uuid: "uuid-foo"',
kuery: 'kibana.alert.uuid: "uuid-foo,asia"',
rangeFrom: expect.stringMatching(ISO_DATE_REGEX),
spaceId: 'irrelevant',
});
expect(alertsLocatorMock.getLocation).toHaveBeenNthCalledWith(2, {
baseUrl: 'https://kibana.dev',
kuery: 'kibana.alert.uuid: "uuid-bar,asia"',
rangeFrom: expect.stringMatching(ISO_DATE_REGEX),
spaceId: 'irrelevant',
});

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import numeral from '@elastic/numeral';
import { getEcsGroups } from '@kbn/observability-alerting-rule-utils';
import {
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
@ -184,6 +185,7 @@ export const getRuleExecutor = ({
[SLO_ID_FIELD]: slo.id,
[SLO_REVISION_FIELD]: slo.revision,
[SLO_INSTANCE_ID_FIELD]: instanceId,
...getEcsGroups(groups),
},
});

View file

@ -95,6 +95,7 @@
"@kbn/react-kibana-context-render",
"@kbn/core-application-browser",
"@kbn/core-theme-browser",
"@kbn/ebt-tools"
"@kbn/ebt-tools",
"@kbn/observability-alerting-rule-utils"
]
}