Skip to content

Commit

Permalink
[ResponseOps][Alerts] Fix muted alerts query using wrong filter (#204182
Browse files Browse the repository at this point in the history
)

## Summary

Reverts the `getMutedAlerts` filter to use rule ids instead of rule type
ids that caused the muted alerts state to be always empty.

## To verify

1. Create a rule that fire alerts
2. Navigate to `Stack management > Alerts`
3. Click on any alert actions menu (•••)
4. Toggle on and off the alert muted state and check that the action
updates accordingly and the slashed bell icon appears in the status cell
when muted

## Release note

Fix alert mute/unmute action
  • Loading branch information
umbopepato authored Dec 18, 2024
1 parent ff3a600 commit d5af2e0
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,28 @@ describe('getMutedAlerts', () => {
Array [
"/internal/alerting/rules/_find",
Object {
"body": "{\\"rule_type_ids\\":[\\"foo\\"],\\"fields\\":[\\"id\\",\\"mutedInstanceIds\\"],\\"page\\":1,\\"per_page\\":1}",
"body": "{\\"filter\\":\\"{\\\\\\"type\\\\\\":\\\\\\"function\\\\\\",\\\\\\"function\\\\\\":\\\\\\"is\\\\\\",\\\\\\"arguments\\\\\\":[{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert.id\\\\\\",\\\\\\"isQuoted\\\\\\":false},{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert:foo\\\\\\",\\\\\\"isQuoted\\\\\\":false}]}\\",\\"fields\\":[\\"id\\",\\"mutedInstanceIds\\"],\\"page\\":1,\\"per_page\\":1}",
"signal": undefined,
},
]
`);
});

test('should call find API with multiple ruleIds', async () => {
const result = await getMutedAlerts(http, { ruleIds: ['foo', 'bar'] });

expect(result).toEqual({
page: 1,
per_page: 10,
total: 0,
data: [],
});

expect(http.post.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_find",
Object {
"body": "{\\"filter\\":\\"{\\\\\\"type\\\\\\":\\\\\\"function\\\\\\",\\\\\\"function\\\\\\":\\\\\\"or\\\\\\",\\\\\\"arguments\\\\\\":[{\\\\\\"type\\\\\\":\\\\\\"function\\\\\\",\\\\\\"function\\\\\\":\\\\\\"is\\\\\\",\\\\\\"arguments\\\\\\":[{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert.id\\\\\\",\\\\\\"isQuoted\\\\\\":false},{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert:foo\\\\\\",\\\\\\"isQuoted\\\\\\":false}]},{\\\\\\"type\\\\\\":\\\\\\"function\\\\\\",\\\\\\"function\\\\\\":\\\\\\"is\\\\\\",\\\\\\"arguments\\\\\\":[{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert.id\\\\\\",\\\\\\"isQuoted\\\\\\":false},{\\\\\\"type\\\\\\":\\\\\\"literal\\\\\\",\\\\\\"value\\\\\\":\\\\\\"alert:bar\\\\\\",\\\\\\"isQuoted\\\\\\":false}]}]}\\",\\"fields\\":[\\"id\\",\\"mutedInstanceIds\\"],\\"page\\":1,\\"per_page\\":2}",
"signal": undefined,
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { HttpStart } from '@kbn/core-http-browser';
import { nodeBuilder } from '@kbn/es-query';

const INTERNAL_FIND_RULES_URL = '/internal/alerting/rules/_find';

Expand All @@ -23,9 +24,12 @@ export const getMutedAlerts = async (
params: { ruleIds: string[] },
signal?: AbortSignal
) => {
const filterNode = nodeBuilder.or(
params.ruleIds.map((id) => nodeBuilder.is('alert.id', `alert:${id}`))
);
return http.post<FindRulesResponse>(INTERNAL_FIND_RULES_URL, {
body: JSON.stringify({
rule_type_ids: params.ruleIds,
filter: JSON.stringify(filterNode),
fields: ['id', 'mutedInstanceIds'],
page: 1,
per_page: params.ruleIds.length,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
loadTestFile(require.resolve('./builtin_alert_types'));
loadTestFile(require.resolve('./mustache_templates.ts'));
loadTestFile(require.resolve('./notify_when'));
loadTestFile(require.resolve('./muted_alerts'));
loadTestFile(require.resolve('./event_log_alerts'));
loadTestFile(require.resolve('./snooze'));
loadTestFile(require.resolve('./unsnooze'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 expect from '@kbn/expect';
import { ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers';
import { ALERT_INSTANCE_ID, ALERT_RULE_UUID, ALERT_STATUS } from '@kbn/rule-data-utils';
import { nodeBuilder } from '@kbn/es-query';
import { Spaces } from '../../../scenarios';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';

const alertAsDataIndex = '.internal.alerts-observability.test.alerts.alerts-default-000001';

// eslint-disable-next-line import/no-default-export
export default function createDisableRuleTests({ getService }: FtrProviderContext) {
const es = getService('es');
const retry = getService('retry');
const supertest = getService('supertest');

describe('mutedAlerts', () => {
const objectRemover = new ObjectRemover(supertest);

const createRule = async () => {
const { body: createdRule } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
rule_type_id: 'test.always-firing-alert-as-data',
schedule: { interval: '24h' },
throttle: undefined,
notify_when: undefined,
params: {
index: ES_TEST_INDEX_NAME,
reference: 'test',
},
})
)
.expect(200);

objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');
return createdRule.id;
};

const getAlerts = async () => {
const {
hits: { hits: alerts },
} = await es.search({
index: alertAsDataIndex,
body: { query: { match_all: {} } },
});

return alerts;
};

afterEach(async () => {
await es.deleteByQuery({
index: alertAsDataIndex,
query: {
match_all: {},
},
conflicts: 'proceed',
ignore_unavailable: true,
});
await objectRemover.removeAll();
});

it('should reflect muted alert instance ids in rule', async () => {
const createdRule1 = await createRule();
const createdRule2 = await createRule();

let alerts: any[] = [];

await retry.try(async () => {
alerts = await getAlerts();

expect(alerts.length).greaterThan(2);
alerts.forEach((activeAlert: any) => {
expect(activeAlert._source[ALERT_STATUS]).eql('active');
});
});

const alertFromRule1 = alerts.find(
(alert: any) =>
alert._source[ALERT_STATUS] === 'active' &&
alert._source[ALERT_RULE_UUID] === createdRule1
);

await supertest
.post(
`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${encodeURIComponent(
createdRule1
)}/alert/${encodeURIComponent(alertFromRule1._source['kibana.alert.instance.id'])}/_mute`
)
.set('kbn-xsrf', 'foo')
.expect(204);

const ruleUuids = [createdRule1, createdRule2];

const filterNode = nodeBuilder.or(
ruleUuids.map((id) => nodeBuilder.is('alert.id', `alert:${id}`))
);
const { body: rules } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_find`)
.set('kbn-xsrf', 'foo')
.send({
filter: JSON.stringify(filterNode),
fields: ['id', 'mutedInstanceIds'],
page: 1,
per_page: ruleUuids.length,
});

expect(rules.data.length).to.be(2);
const mutedRule = rules.data.find((rule: { id: string }) => rule.id === createdRule1);
expect(mutedRule.muted_alert_ids).to.contain(alertFromRule1._source[ALERT_INSTANCE_ID]);
});
});
}

0 comments on commit d5af2e0

Please sign in to comment.