[ResponseOps][Alerting] A redundant summary alert is shown on rule details alerts list (#150088)

Resolves https://github.com/elastic/kibana/issues/149824

## Summary

Added a filter to filter out logs that have an action set to
`execute-action` in the `alert_summary` route on the rules details
page.
Updated how we log summary alerts by removing the `alertId` and `group`
fields. I added a new summary field to `kibana.alerting` that includes
the counts for new, ongoing, and recovered alerts.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### To verify

- Create a rule that has Alert Summary and let the rule run.
- Click on the rule to navigate to the rule details page and look at the
`Alerts` tab. Verify that the summary alert is not listed in the table.

**This is a screen shot of the table in the issue:**
We want to verify that the last row is no longer there.
<img width="1728" alt="Screen Shot 2023-02-01 at 3 02 21 PM"
src="https://user-images.githubusercontent.com/109488926/216150854-590e084d-3519-4785-8375-00a7455c319f.png">
This commit is contained in:
Alexi Doak 2023-02-08 08:10:09 -05:00 committed by GitHub
parent e770455173
commit 1856323bbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 129 additions and 4 deletions

View file

@ -54,8 +54,13 @@ interface AlertOpts {
interface ActionOpts {
id: string;
typeId: string;
alertId: string;
alertId?: string;
alertGroup?: string;
alertSummary?: {
new: number;
ongoing: number;
recovered: number;
};
}
export class AlertingEventLogger {
@ -278,6 +283,7 @@ export function createActionExecuteRecord(context: RuleContextOpts, action: Acti
},
],
ruleName: context.ruleName,
alertSummary: action.alertSummary,
});
}

View file

@ -191,6 +191,11 @@ describe('createAlertEventLogRecordObject', () => {
},
],
spaceId: 'default',
alertSummary: {
new: 2,
ongoing: 3,
recovered: 1,
},
})
).toStrictEqual({
event: {
@ -214,6 +219,17 @@ describe('createAlertEventLogRecordObject', () => {
alerting: {
action_group_id: 'group 1',
instance_id: 'test1',
summary: {
new: {
count: 2,
},
ongoing: {
count: 3,
},
recovered: {
count: 1,
},
},
},
saved_objects: [
{

View file

@ -36,6 +36,11 @@ interface CreateAlertEventLogRecordParams {
relation?: string;
}>;
flapping?: boolean;
alertSummary?: {
new: number;
ongoing: number;
recovered: number;
};
}
export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecordParams): Event {
@ -52,13 +57,23 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor
consumer,
spaceId,
flapping,
alertSummary,
} = params;
const alerting =
params.instanceId || group
params.instanceId || group || alertSummary
? {
alerting: {
...(params.instanceId ? { instance_id: params.instanceId } : {}),
...(group ? { action_group_id: group } : {}),
...(alertSummary
? {
summary: {
new: { count: alertSummary.new },
ongoing: { count: alertSummary.ongoing },
recovered: { count: alertSummary.recovered },
},
}
: {}),
},
}
: undefined;

View file

@ -56,6 +56,8 @@ export async function getAlertSummary(
start: parsedDateStart.toISOString(),
sort: [{ sort_field: '@timestamp', sort_order: 'desc' }],
end: dateNow.toISOString(),
// filter out execute-action event logs
filter: 'NOT event.action: execute-action AND event.provider: alerting',
},
rule.legacyId !== null ? [rule.legacyId] : undefined
),

View file

@ -222,6 +222,7 @@ describe('getAlertSummary()', () => {
],
Object {
"end": "2019-02-12T21:01:22.479Z",
"filter": "NOT event.action: execute-action AND event.provider: alerting",
"page": 1,
"per_page": 10000,
"sort": Array [
@ -264,6 +265,7 @@ describe('getAlertSummary()', () => {
],
Object {
"end": "2019-02-12T21:01:22.479Z",
"filter": "NOT event.action: execute-action AND event.provider: alerting",
"page": 1,
"per_page": 10000,
"sort": Array [

View file

@ -907,6 +907,11 @@ describe('Execution Handler', () => {
],
]
`);
expect(alertingEventLogger.logAction).toBeCalledWith({
alertSummary: { new: 1, ongoing: 0, recovered: 0 },
id: '1',
typeId: 'testActionTypeId',
});
});
test('skips summary actions (per rule run) when there is no alerts', async () => {
@ -946,6 +951,7 @@ describe('Execution Handler', () => {
expect(getSummarizedAlertsMock).not.toHaveBeenCalled();
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
});
test('triggers summary actions (custom interval)', async () => {
@ -1030,6 +1036,11 @@ describe('Execution Handler', () => {
],
]
`);
expect(alertingEventLogger.logAction).toBeCalledWith({
alertSummary: { new: 1, ongoing: 0, recovered: 0 },
id: '1',
typeId: 'testActionTypeId',
});
});
test('does not trigger summary actions if it is still being throttled (custom interval)', async () => {
@ -1075,6 +1086,7 @@ describe('Execution Handler', () => {
);
expect(getSummarizedAlertsMock).not.toHaveBeenCalled();
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
});
test('removes the obsolete actions from the task state', async () => {

View file

@ -239,8 +239,11 @@ export class ExecutionHandler<
logActions.push({
id: action.id,
typeId: action.actionTypeId,
alertId: 'summary',
alertGroup: action.group,
alertSummary: {
new: summarizedAlerts.new.count,
ongoing: summarizedAlerts.ongoing.count,
recovered: summarizedAlerts.recovered.count,
},
});
} else {
const executableAlert = alert!;

View file

@ -271,6 +271,31 @@
"outcome": {
"type": "keyword",
"ignore_above": 1024
},
"summary": {
"properties": {
"new": {
"properties": {
"count": {
"type": "long"
}
}
},
"ongoing": {
"properties": {
"count": {
"type": "long"
}
}
},
"recovered": {
"properties": {
"count": {
"type": "long"
}
}
}
}
}
}
},

View file

@ -116,6 +116,25 @@ export const EventSchema = schema.maybe(
action_subgroup: ecsString(),
status: ecsString(),
outcome: ecsString(),
summary: schema.maybe(
schema.object({
new: schema.maybe(
schema.object({
count: ecsStringOrNumber(),
})
),
ongoing: schema.maybe(
schema.object({
count: ecsStringOrNumber(),
})
),
recovered: schema.maybe(
schema.object({
count: ecsStringOrNumber(),
})
),
})
),
})
),
alert: schema.maybe(

View file

@ -54,6 +54,31 @@ exports.EcsCustomPropertyMappings = {
type: 'keyword',
ignore_above: 1024,
},
summary: {
properties: {
new: {
properties: {
count: {
type: 'long',
},
},
},
ongoing: {
properties: {
count: {
type: 'long',
},
},
},
recovered: {
properties: {
count: {
type: 'long',
},
},
},
},
},
},
},
alert: {