Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps][Alerts] Fix muted alerts query using wrong filter #204182

Merged
merged 12 commits into from
Dec 18, 2024
Merged
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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snapshot in the tests will fail. Could you also add a test where there are multiple ruleIds? The current one only tests ruleIds: ['foo'].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! 🙂

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]);
});
});
}
Loading