diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts b/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts index 4f0aa0fb003df..ab3edece0becc 100644 --- a/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts +++ b/x-pack/plugins/alerting/server/alerts_client/lib/get_summarized_alerts_query.ts @@ -39,6 +39,7 @@ import { import { SummarizedAlertsChunk, ScopedQueryAlerts } from '../..'; import { FormatAlert } from '../../types'; import { expandFlattenedAlert } from './format_alert'; +import { injectAnalyzeWildcard } from './inject_analyze_wildcard'; const MAX_ALERT_DOCS_TO_RETURN = 100; enum AlertTypes { @@ -310,9 +311,14 @@ export const getQueryByScopedQueries = ({ return; } - const scopedQueryFilter = generateAlertsFilterDSL({ - query: scopedQuery as AlertsFilter['query'], - })[0] as { bool: BoolQuery }; + const scopedQueryFilter = generateAlertsFilterDSL( + { + query: scopedQuery as AlertsFilter['query'], + }, + { + analyzeWildcard: true, + } + )[0] as { bool: BoolQuery }; aggs[id] = { filter: { @@ -324,6 +330,7 @@ export const getQueryByScopedQueries = ({ aggs: { alertId: { top_hits: { + size: MAX_ALERT_DOCS_TO_RETURN, _source: { includes: [ALERT_UUID], }, @@ -340,11 +347,19 @@ export const getQueryByScopedQueries = ({ }; }; -const generateAlertsFilterDSL = (alertsFilter: AlertsFilter): QueryDslQueryContainer[] => { +const generateAlertsFilterDSL = ( + alertsFilter: AlertsFilter, + options?: { analyzeWildcard?: boolean } +): QueryDslQueryContainer[] => { const filter: QueryDslQueryContainer[] = []; + const { analyzeWildcard = false } = options || {}; if (alertsFilter.query) { - filter.push(JSON.parse(alertsFilter.query.dsl!)); + const parsedQuery = JSON.parse(alertsFilter.query.dsl!); + if (analyzeWildcard) { + injectAnalyzeWildcard(parsedQuery); + } + filter.push(parsedQuery); } if (alertsFilter.timeframe) { filter.push( diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts new file mode 100644 index 0000000000000..1e1db14d928ba --- /dev/null +++ b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.test.ts @@ -0,0 +1,169 @@ +/* + * 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 { injectAnalyzeWildcard } from './inject_analyze_wildcard'; + +const getQuery = (query?: string) => { + return { + bool: { + must: [], + filter: [ + { + bool: { + filter: [ + { + bool: { + should: [ + { + query_string: { + fields: ['kibana.alert.instance.id'], + query: query || '*elastic*', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + 'kibana.alert.action_group': 'test', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + ], + should: [], + must_not: [ + { + match_phrase: { + _id: 'assdasdasd', + }, + }, + ], + }, + }; +}; +describe('injectAnalyzeWildcard', () => { + test('should inject analyze_wildcard field', () => { + const query = getQuery(); + injectAnalyzeWildcard(query); + expect(query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "query_string": Object { + "analyze_wildcard": true, + "fields": Array [ + "kibana.alert.instance.id", + ], + "query": "*elastic*", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "kibana.alert.action_group": "test", + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [ + Object { + "match_phrase": Object { + "_id": "assdasdasd", + }, + }, + ], + "should": Array [], + }, + } + `); + }); + + test('should not inject analyze_wildcard if the query does not contain *', () => { + const query = getQuery('test'); + injectAnalyzeWildcard(query); + expect(query).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "query_string": Object { + "fields": Array [ + "kibana.alert.instance.id", + ], + "query": "test", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "kibana.alert.action_group": "test", + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [ + Object { + "match_phrase": Object { + "_id": "assdasdasd", + }, + }, + ], + "should": Array [], + }, + } + `); + }); +}); diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts new file mode 100644 index 0000000000000..58a4f89948973 --- /dev/null +++ b/x-pack/plugins/alerting/server/alerts_client/lib/inject_analyze_wildcard.ts @@ -0,0 +1,30 @@ +/* + * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +export const injectAnalyzeWildcard = (query: QueryDslQueryContainer): void => { + if (!query) { + return; + } + + if (Array.isArray(query)) { + return query.forEach((child) => injectAnalyzeWildcard(child)); + } + + if (typeof query === 'object') { + Object.entries(query).forEach(([key, value]) => { + if (key !== 'query_string') { + return injectAnalyzeWildcard(value); + } + + if (typeof value.query === 'string' && value.query.includes('*')) { + value.analyze_wildcard = true; + } + }); + } +}; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts index 6797b4f57d508..e377fb3209d63 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.test.ts @@ -135,6 +135,21 @@ describe('MaintenanceWindowClient - update', () => { eventEndTime: '2023-03-05T01:00:00.000Z', }) ); + + expect(uiSettings.get).toHaveBeenCalledTimes(3); + expect(uiSettings.get.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "query:allowLeadingWildcards", + ], + Array [ + "query:queryString:options", + ], + Array [ + "courier:ignoreFilterIfFieldNotInIndex", + ], + ] + `); }); it('should not regenerate all events if rrule and duration did not change', async () => { diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts index afff024b186c2..cb9d5ded1f7b2 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/update_maintenance_window.ts @@ -10,6 +10,7 @@ import Boom from '@hapi/boom'; import { buildEsQuery, Filter } from '@kbn/es-query'; import type { MaintenanceWindowClientContext } from '../../../../../common'; import { getScopedQueryErrorMessage } from '../../../../../common'; +import { getEsQueryConfig } from '../../../../lib/get_es_query_config'; import type { MaintenanceWindow } from '../../types'; import { generateMaintenanceWindowEvents, @@ -45,9 +46,10 @@ async function updateWithOCC( context: MaintenanceWindowClientContext, params: UpdateMaintenanceWindowParams ): Promise { - const { savedObjectsClient, getModificationMetadata, logger } = context; + const { savedObjectsClient, getModificationMetadata, logger, uiSettings } = context; const { id, data } = params; const { title, enabled, duration, rRule, categoryIds, scopedQuery } = data; + const esQueryConfig = await getEsQueryConfig(uiSettings); try { updateMaintenanceWindowParamsSchema.validate(params); @@ -62,7 +64,8 @@ async function updateWithOCC( buildEsQuery( undefined, [{ query: scopedQuery.kql, language: 'kuery' }], - scopedQuery.filters as Filter[] + scopedQuery.filters as Filter[], + esQueryConfig ) ); scopedQueryWithGeneratedValue = { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/maintenance_window_scoped_query.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/maintenance_window_scoped_query.ts index 03880f79b5b14..2c43649eb7822 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/maintenance_window_scoped_query.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/maintenance_window_scoped_query.ts @@ -245,5 +245,72 @@ export default function maintenanceWindowScopedQueryTests({ getService }: FtrPro retry, }); }); + + it('should associate alerts when scoped query contains wildcards', async () => { + await createMaintenanceWindow({ + supertest, + objectRemover, + overwrites: { + scoped_query: { + kql: 'kibana.alert.rule.name: *test*', + filters: [], + }, + category_ids: ['management'], + }, + }); + + // Create action and rule + const action = await await createAction({ + supertest, + objectRemover, + }); + + const { body: rule } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + name: 'rule-test-rule', + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '24h' }, + tags: ['test'], + throttle: undefined, + notify_when: 'onActiveAlert', + params: { + index: alertAsDataIndex, + reference: 'test', + }, + actions: [ + { + id: action.id, + group: 'default', + params: {}, + }, + { + id: action.id, + group: 'recovered', + params: {}, + }, + ], + }) + ) + .expect(200); + + objectRemover.add(Spaces.space1.id, rule.id, 'rule', 'alerting'); + + // Run the first time - active + await getRuleEvents({ + id: rule.id, + activeInstance: 2, + retry, + getService, + }); + + await expectNoActionsFired({ + id: rule.id, + supertest, + retry, + }); + }); }); }