diff --git a/x-pack/plugins/alerting/server/constants/plugin.ts b/x-pack/plugins/alerting/common/constants/plugin.ts similarity index 90% rename from x-pack/plugins/alerting/server/constants/plugin.ts rename to x-pack/plugins/alerting/common/constants/plugin.ts index 05fd33ebf8dd3..e8136f257d465 100644 --- a/x-pack/plugins/alerting/server/constants/plugin.ts +++ b/x-pack/plugins/alerting/common/constants/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { LicenseType } from '@kbn/licensing-plugin/server'; +import type { LicenseType } from '@kbn/licensing-plugin/server'; export const PLUGIN = { ID: 'alerting', diff --git a/x-pack/plugins/alerting/common/maintenance_window.ts b/x-pack/plugins/alerting/common/maintenance_window.ts index 1f5bffea77081..8b7646670cc76 100644 --- a/x-pack/plugins/alerting/common/maintenance_window.ts +++ b/x-pack/plugins/alerting/common/maintenance_window.ts @@ -14,6 +14,13 @@ export enum MaintenanceWindowStatus { Archived = 'archived', } +export const filterStateStore = { + APP_STATE: 'appState', + GLOBAL_STATE: 'globalState', +} as const; + +export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore]; + export interface MaintenanceWindowModificationMetadata { createdBy: string | null; updatedBy: string | null; @@ -26,6 +33,23 @@ export interface DateRange { lte: string; } +export interface ScopeQueryFilter { + query?: Record; + meta: Record; + $state?: { + store: FilterStateStore; + }; +} + +export interface ScopedQueryAttributes { + kql: string; + filters: ScopeQueryFilter[]; + dsl?: string; +} + +/** + * @deprecated Use the data/maintenance_window types instead + */ export interface MaintenanceWindowSOProperties { title: string; enabled: boolean; @@ -34,11 +58,18 @@ export interface MaintenanceWindowSOProperties { events: DateRange[]; rRule: RRuleParams; categoryIds?: string[] | null; + scopedQuery?: ScopedQueryAttributes | null; } +/** + * @deprecated Use the data/maintenance_window types instead + */ export type MaintenanceWindowSOAttributes = MaintenanceWindowSOProperties & MaintenanceWindowModificationMetadata; +/** + * @deprecated Use the application/maintenance_window types instead + */ export type MaintenanceWindow = MaintenanceWindowSOAttributes & { status: MaintenanceWindowStatus; eventStartTime: string | null; diff --git a/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/latest.ts b/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/latest.ts new file mode 100644 index 0000000000000..6c32b4867cc0d --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/latest.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { filterStateStore } from './v1'; +export type { FilterStateStore } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/v1.ts b/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/v1.ts new file mode 100644 index 0000000000000..bce6890c22f2c --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/alerts_filter_query/constants/v1.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export const filterStateStore = { + APP_STATE: 'appState', + GLOBAL_STATE: 'globalState', +} as const; + +export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore]; diff --git a/x-pack/plugins/alerting/common/routes/alerts_filter_query/index.ts b/x-pack/plugins/alerting/common/routes/alerts_filter_query/index.ts new file mode 100644 index 0000000000000..093299dbe66f2 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/alerts_filter_query/index.ts @@ -0,0 +1,14 @@ +/* + * 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. + */ + +export { filterStateStore } from './constants/latest'; +export type { FilterStateStore } from './constants/latest'; +export { alertsFilterQuerySchema } from './schemas/latest'; + +export { filterStateStore as filterStateStoreV1 } from './constants/v1'; +export type { FilterStateStore as FilterStateStoreV1 } from './constants/v1'; +export { alertsFilterQuerySchema as alertsFilterQuerySchemaV1 } from './schemas/v1'; diff --git a/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/latest.ts new file mode 100644 index 0000000000000..b9ae85bd590cf --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { alertsFilterQuerySchema } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/v1.ts new file mode 100644 index 0000000000000..08614efb96b70 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/alerts_filter_query/schemas/v1.ts @@ -0,0 +1,28 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { filterStateStore } from '..'; + +export const alertsFilterQuerySchema = schema.object({ + kql: schema.string(), + filters: schema.arrayOf( + schema.object({ + query: schema.maybe(schema.recordOf(schema.string(), schema.any())), + meta: schema.recordOf(schema.string(), schema.any()), + $state: schema.maybe( + schema.object({ + store: schema.oneOf([ + schema.literal(filterStateStore.APP_STATE), + schema.literal(filterStateStore.GLOBAL_STATE), + ]), + }) + ), + }) + ), + dsl: schema.maybe(schema.string()), +}); diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/create/schemas/v1.ts index f10eb420963ed..a715bd13f2f75 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/create/schemas/v1.ts @@ -8,10 +8,12 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowCategoryIdsSchemaV1 } from '../../../shared'; import { rRuleRequestSchemaV1 } from '../../../../r_rule'; +import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; export const createBodySchema = schema.object({ title: schema.string(), duration: schema.number(), r_rule: rRuleRequestSchemaV1, category_ids: maintenanceWindowCategoryIdsSchemaV1, + scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)), }); diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/update/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/update/schemas/v1.ts index 970cd34424576..ac61048d760a1 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/apis/update/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/apis/update/schemas/v1.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowCategoryIdsSchemaV1 } from '../../../shared'; import { rRuleRequestSchemaV1 } from '../../../../r_rule'; +import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; export const updateParamsSchema = schema.object({ id: schema.string(), @@ -19,4 +20,5 @@ export const updateBodySchema = schema.object({ duration: schema.maybe(schema.number()), r_rule: schema.maybe(rRuleRequestSchemaV1), category_ids: maintenanceWindowCategoryIdsSchemaV1, + scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)), }); diff --git a/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts index 410a1dc1e439d..648b2b806978f 100644 --- a/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/maintenance_window/response/schemas/v1.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowStatusV1 } from '..'; import { maintenanceWindowCategoryIdsSchemaV1 } from '../../shared'; import { rRuleResponseSchemaV1 } from '../../../r_rule'; +import { alertsFilterQuerySchemaV1 } from '../../../alerts_filter_query'; export const maintenanceWindowEventSchema = schema.object({ gte: schema.string(), @@ -36,4 +37,5 @@ export const maintenanceWindowResponseSchema = schema.object({ schema.literal(maintenanceWindowStatusV1.ARCHIVED), ]), category_ids: maintenanceWindowCategoryIdsSchemaV1, + scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)), }); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts index 751f2e8c38780..30a294d5c0527 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; import { notifyWhenSchemaV1 } from '../../../response'; -import { filterStateStore } from '../../../common/constants/v1'; +import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; export const actionFrequencySchema = schema.object({ summary: schema.boolean(), @@ -17,26 +17,7 @@ export const actionFrequencySchema = schema.object({ }); export const actionAlertsFilterSchema = schema.object({ - query: schema.maybe( - schema.object({ - kql: schema.string(), - filters: schema.arrayOf( - schema.object({ - query: schema.maybe(schema.recordOf(schema.string(), schema.any())), - meta: schema.recordOf(schema.string(), schema.any()), - $state: schema.maybe( - schema.object({ - store: schema.oneOf([ - schema.literal(filterStateStore.APP_STATE), - schema.literal(filterStateStore.GLOBAL_STATE), - ]), - }) - ), - }) - ), - dsl: schema.maybe(schema.string()), - }) - ), + query: schema.maybe(alertsFilterQuerySchemaV1), timeframe: schema.maybe( schema.object({ days: schema.arrayOf( diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 0e043aa217667..1c7b202f59060 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -7,13 +7,13 @@ import { schema } from '@kbn/config-schema'; import { rRuleResponseSchemaV1 } from '../../../r_rule'; +import { alertsFilterQuerySchemaV1 } from '../../../alerts_filter_query'; import { ruleNotifyWhen as ruleNotifyWhenV1, ruleExecutionStatusValues as ruleExecutionStatusValuesV1, ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1, ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1, ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1, - filterStateStore as filterStateStoreV1, } from '../../common/constants/v1'; import { validateNotifyWhenV1 } from '../../validation'; @@ -41,25 +41,7 @@ const actionFrequencySchema = schema.object({ }); const actionAlertsFilterSchema = schema.object({ - query: schema.maybe( - schema.object({ - kql: schema.string(), - filters: schema.arrayOf( - schema.object({ - query: schema.maybe(schema.recordOf(schema.string(), schema.any())), - meta: schema.recordOf(schema.string(), schema.any()), - $state: schema.maybe( - schema.object({ - store: schema.oneOf([ - schema.literal(filterStateStoreV1.APP_STATE), - schema.literal(filterStateStoreV1.GLOBAL_STATE), - ]), - }) - ), - }) - ), - }) - ), + query: schema.maybe(alertsFilterQuerySchemaV1), timeframe: schema.maybe( schema.object({ days: schema.arrayOf( diff --git a/x-pack/plugins/alerting/server/application/alerts_filter_query/constants.ts b/x-pack/plugins/alerting/server/application/alerts_filter_query/constants.ts new file mode 100644 index 0000000000000..bce6890c22f2c --- /dev/null +++ b/x-pack/plugins/alerting/server/application/alerts_filter_query/constants.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export const filterStateStore = { + APP_STATE: 'appState', + GLOBAL_STATE: 'globalState', +} as const; + +export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore]; diff --git a/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/alerts_filter_query_schemas.ts b/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/alerts_filter_query_schemas.ts new file mode 100644 index 0000000000000..bf5a24b6e4399 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/alerts_filter_query_schemas.ts @@ -0,0 +1,28 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { filterStateStore } from '../constants'; + +export const alertsFilterQuerySchema = schema.object({ + kql: schema.string(), + filters: schema.arrayOf( + schema.object({ + query: schema.maybe(schema.recordOf(schema.string(), schema.any())), + meta: schema.recordOf(schema.string(), schema.any()), + $state: schema.maybe( + schema.object({ + store: schema.oneOf([ + schema.literal(filterStateStore.APP_STATE), + schema.literal(filterStateStore.GLOBAL_STATE), + ]), + }) + ), + }) + ), + dsl: schema.maybe(schema.string()), +}); diff --git a/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/index.ts b/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/index.ts new file mode 100644 index 0000000000000..5042e3e8265a6 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/alerts_filter_query/schemas/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { alertsFilterQuerySchema } from './alerts_filter_query_schemas'; diff --git a/x-pack/plugins/alerting/server/application/alerts_filter_query/types/alerts_filter_query.ts b/x-pack/plugins/alerting/server/application/alerts_filter_query/types/alerts_filter_query.ts new file mode 100644 index 0000000000000..926d27e89fa94 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/alerts_filter_query/types/alerts_filter_query.ts @@ -0,0 +1,11 @@ +/* + * 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 { TypeOf } from '@kbn/config-schema'; +import { alertsFilterQuerySchema } from '../schemas/alerts_filter_query_schemas'; + +export type AlertsFilterQuery = TypeOf; diff --git a/x-pack/plugins/alerting/server/application/alerts_filter_query/types/index.ts b/x-pack/plugins/alerting/server/application/alerts_filter_query/types/index.ts new file mode 100644 index 0000000000000..2553fd0005904 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/alerts_filter_query/types/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { AlertsFilterQuery } from './alerts_filter_query'; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/constants.ts b/x-pack/plugins/alerting/server/application/maintenance_window/constants.ts index caa1616d10a7c..4b5920d645584 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/constants.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/constants.ts @@ -13,8 +13,12 @@ export const maintenanceWindowStatus = { } as const; export const maintenanceWindowCategoryIdTypes = { - KIBANA: 'kibana', OBSERVABILITY: 'observability', SECURITY_SOLUTION: 'securitySolution', MANAGEMENT: 'management', } as const; + +export const filterStateStore = { + APP_STATE: 'appState', + GLOBAL_STATE: 'globalState', +} as const; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/lib/generate_maintenance_window_events.ts b/x-pack/plugins/alerting/server/application/maintenance_window/lib/generate_maintenance_window_events.ts index 99f8e99f3f5e3..78227dab5864a 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/lib/generate_maintenance_window_events.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/lib/generate_maintenance_window_events.ts @@ -8,7 +8,8 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { RRule, Weekday } from '@kbn/rrule'; -import { RRuleParams, MaintenanceWindowSOAttributes, DateRange } from '../../../../common'; +import { RRuleParams, DateRange } from '../../../../common'; +import { MaintenanceWindow } from '../types'; export interface GenerateMaintenanceWindowEventsParams { rRule: RRuleParams; @@ -58,7 +59,7 @@ export const shouldRegenerateEvents = ({ rRule, duration, }: { - maintenanceWindow: MaintenanceWindowSOAttributes; + maintenanceWindow: MaintenanceWindow; rRule?: RRuleParams; duration?: number; }): boolean => { diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts index 1de657bdfd6ef..af04b62bfc7d9 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.test.ts @@ -133,6 +133,124 @@ describe('MaintenanceWindowClient - create', () => { ); }); + it('should create maintenance window with scoped query', async () => { + jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z')); + + const mockMaintenanceWindow = getMockMaintenanceWindow({ + expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(), + }); + + savedObjectsClient.create.mockResolvedValueOnce({ + attributes: mockMaintenanceWindow, + version: '123', + id: 'test-id', + } as unknown as SavedObject); + + await createMaintenanceWindow(mockContext, { + data: { + title: mockMaintenanceWindow.title, + duration: mockMaintenanceWindow.duration, + rRule: mockMaintenanceWindow.rRule as CreateMaintenanceWindowParams['data']['rRule'], + categoryIds: ['observability', 'securitySolution'], + scopedQuery: { + kql: "_id: '1234'", + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + key: 'kibana.alert.action_group', + field: 'kibana.alert.action_group', + params: { + query: 'test', + }, + type: 'phrase', + }, + $state: { + store: 'appState', + }, + query: { + match_phrase: { + 'kibana.alert.action_group': 'test', + }, + }, + }, + ], + }, + }, + }); + + expect(savedObjectsClient.create).toHaveBeenLastCalledWith( + MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, + expect.objectContaining({ + title: mockMaintenanceWindow.title, + duration: mockMaintenanceWindow.duration, + rRule: mockMaintenanceWindow.rRule, + enabled: true, + expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(), + categoryIds: ['observability', 'securitySolution'], + ...updatedMetadata, + }), + { + id: expect.any(String), + } + ); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.kql + ).toEqual(`_id: '1234'`); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.filters[0] + ).toEqual({ + $state: { store: 'appState' }, + meta: { + alias: null, + disabled: false, + field: 'kibana.alert.action_group', + key: 'kibana.alert.action_group', + negate: false, + params: { query: 'test' }, + type: 'phrase', + }, + query: { match_phrase: { 'kibana.alert.action_group': 'test' } }, + }); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.dsl + ).toMatchInlineSnapshot( + `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"_id\\":\\"'1234'\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"kibana.alert.action_group\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"` + ); + }); + + it('should throw if trying to create a maintenance window with invalid scoped query', async () => { + jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z')); + + const mockMaintenanceWindow = getMockMaintenanceWindow({ + expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(), + }); + + await expect(async () => { + await createMaintenanceWindow(mockContext, { + data: { + title: mockMaintenanceWindow.title, + duration: mockMaintenanceWindow.duration, + rRule: mockMaintenanceWindow.rRule as CreateMaintenanceWindowParams['data']['rRule'], + categoryIds: ['observability', 'securitySolution'], + scopedQuery: { + kql: 'invalid: ', + filters: [], + }, + }, + }); + }).rejects.toThrowErrorMatchingInlineSnapshot(` + "Error validating create maintenance scoped query - Expected \\"(\\", \\"{\\", value, whitespace but end of input found. + invalid: + ---------^" + `); + }); + it('should throw if trying to create a maintenance window with invalid category ids', async () => { jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z')); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.ts index b8fba87b2fc18..78f5e885874b0 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/create_maintenance_window.ts @@ -8,6 +8,7 @@ import moment from 'moment'; import Boom from '@hapi/boom'; import { SavedObjectsUtils } from '@kbn/core/server'; +import { buildEsQuery, Filter } from '@kbn/es-query'; import { generateMaintenanceWindowEvents } from '../../lib/generate_maintenance_window_events'; import type { MaintenanceWindowClientContext } from '../../../../../common'; import type { MaintenanceWindow } from '../../types'; @@ -25,7 +26,7 @@ export async function createMaintenanceWindow( ): Promise { const { data } = params; const { savedObjectsClient, getModificationMetadata, logger } = context; - const { title, duration, rRule, categoryIds } = data; + const { title, duration, rRule, categoryIds, scopedQuery } = data; try { createMaintenanceWindowParamsSchema.validate(params); @@ -33,6 +34,25 @@ export async function createMaintenanceWindow( throw Boom.badRequest(`Error validating create maintenance window data - ${error.message}`); } + let scopedQueryWithGeneratedValue = scopedQuery; + try { + if (scopedQuery) { + const dsl = JSON.stringify( + buildEsQuery( + undefined, + [{ query: scopedQuery.kql, language: 'kuery' }], + scopedQuery.filters as Filter[] + ) + ); + scopedQueryWithGeneratedValue = { + ...scopedQuery, + dsl, + }; + } + } catch (error) { + throw Boom.badRequest(`Error validating create maintenance scoped query - ${error.message}`); + } + const id = SavedObjectsUtils.generateId(); const expirationDate = moment().utc().add(1, 'year').toISOString(); const modificationMetadata = await getModificationMetadata(); @@ -43,6 +63,7 @@ export async function createMaintenanceWindow( enabled: true, expirationDate, categoryIds, + scopedQuery: scopedQueryWithGeneratedValue, rRule: rRule as MaintenanceWindow['rRule'], duration, events, diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts index dcb9150f2385b..b55a4871d0b6f 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/create/schemas/create_maintenance_window_params_schema.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowCategoryIdsSchema } from '../../../schemas'; import { rRuleRequestSchema } from '../../../../r_rule/schemas'; +import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas'; export const createMaintenanceWindowParamsSchema = schema.object({ data: schema.object({ @@ -15,5 +16,6 @@ export const createMaintenanceWindowParamsSchema = schema.object({ duration: schema.number(), rRule: rRuleRequestSchema, categoryIds: maintenanceWindowCategoryIdsSchema, + scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)), }), }); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts index 84120a5ee8c44..1aca8ab24fcda 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/methods/update/schemas/update_maintenance_window_params_schema.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowCategoryIdsSchema } from '../../../schemas'; import { rRuleRequestSchema } from '../../../../r_rule/schemas'; +import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas'; export const updateMaintenanceWindowParamsSchema = schema.object({ id: schema.string(), @@ -17,5 +18,6 @@ export const updateMaintenanceWindowParamsSchema = schema.object({ duration: schema.maybe(schema.number()), rRule: schema.maybe(rRuleRequestSchema), categoryIds: maintenanceWindowCategoryIdsSchema, + scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)), }), }); 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 966808fed57f2..4caef4eca1ba1 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 @@ -207,6 +207,172 @@ describe('MaintenanceWindowClient - update', () => { ); }); + it('should update maintenance window with scoped query', async () => { + jest.useFakeTimers().setSystemTime(new Date(firstTimestamp)); + + const modifiedEvents = [ + { gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T00:12:34.000Z' }, + { gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-01T23:43:21.000Z' }, + ]; + const mockMaintenanceWindow = getMockMaintenanceWindow({ + rRule: { + tzid: 'CET', + dtstart: '2023-03-26T00:00:00.000Z', + freq: Frequency.WEEKLY, + count: 5, + } as MaintenanceWindow['rRule'], + events: modifiedEvents, + expirationDate: moment(new Date(firstTimestamp)).tz('UTC').add(2, 'week').toISOString(), + }); + + savedObjectsClient.get.mockResolvedValue({ + attributes: mockMaintenanceWindow, + version: '123', + id: 'test-id', + } as unknown as SavedObject); + + savedObjectsClient.create.mockResolvedValue({ + attributes: { + ...mockMaintenanceWindow, + ...updatedAttributes, + ...updatedMetadata, + }, + id: 'test-id', + } as unknown as SavedObject); + + await updateMaintenanceWindow(mockContext, { + id: 'test-id', + data: { + scopedQuery: { + kql: "_id: '1234'", + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + key: 'kibana.alert.action_group', + field: 'kibana.alert.action_group', + params: { + query: 'test', + }, + type: 'phrase', + }, + $state: { + store: 'appState', + }, + query: { + match_phrase: { + 'kibana.alert.action_group': 'test', + }, + }, + }, + ], + }, + }, + }); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.kql + ).toEqual(`_id: '1234'`); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.filters[0] + ).toEqual({ + $state: { store: 'appState' }, + meta: { + alias: null, + disabled: false, + field: 'kibana.alert.action_group', + key: 'kibana.alert.action_group', + negate: false, + params: { query: 'test' }, + type: 'phrase', + }, + query: { match_phrase: { 'kibana.alert.action_group': 'test' } }, + }); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.dsl + ).toMatchInlineSnapshot( + `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"_id\\":\\"'1234'\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"kibana.alert.action_group\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"` + ); + }); + + it('should remove maintenance window with scoped query', async () => { + jest.useFakeTimers().setSystemTime(new Date(firstTimestamp)); + + const modifiedEvents = [ + { gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T00:12:34.000Z' }, + { gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-01T23:43:21.000Z' }, + ]; + const mockMaintenanceWindow = getMockMaintenanceWindow({ + rRule: { + tzid: 'CET', + dtstart: '2023-03-26T00:00:00.000Z', + freq: Frequency.WEEKLY, + count: 5, + } as MaintenanceWindow['rRule'], + events: modifiedEvents, + expirationDate: moment(new Date(firstTimestamp)).tz('UTC').add(2, 'week').toISOString(), + }); + + savedObjectsClient.get.mockResolvedValue({ + attributes: mockMaintenanceWindow, + version: '123', + id: 'test-id', + } as unknown as SavedObject); + + savedObjectsClient.create.mockResolvedValue({ + attributes: { + ...mockMaintenanceWindow, + ...updatedAttributes, + ...updatedMetadata, + }, + id: 'test-id', + } as unknown as SavedObject); + + await updateMaintenanceWindow(mockContext, { + id: 'test-id', + data: { + scopedQuery: null, + }, + }); + + expect( + (savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery + ).toBeNull(); + }); + + it('should throw if updating a maintenance window with invalid scoped query', async () => { + jest.useFakeTimers().setSystemTime(new Date(firstTimestamp)); + const mockMaintenanceWindow = getMockMaintenanceWindow({ + expirationDate: moment(new Date(firstTimestamp)).tz('UTC').subtract(1, 'year').toISOString(), + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + attributes: mockMaintenanceWindow, + version: '123', + id: 'test-id', + } as unknown as SavedObject); + + await expect(async () => { + await updateMaintenanceWindow(mockContext, { + id: 'test-id', + data: { + scopedQuery: { + kql: 'invalid: ', + filters: [], + }, + }, + }); + }).rejects.toThrowErrorMatchingInlineSnapshot(` + "Error validating update maintenance scoped query - Expected \\"(\\", \\"{\\", value, whitespace but end of input found. + invalid: + ---------^" + `); + }); + it('should throw if updating a maintenance window that has expired', async () => { jest.useFakeTimers().setSystemTime(new Date(firstTimestamp)); const mockMaintenanceWindow = getMockMaintenanceWindow({ 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 f10381defc9b9..6b7ab69cc8070 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 @@ -7,6 +7,7 @@ import moment from 'moment'; import Boom from '@hapi/boom'; +import { buildEsQuery, Filter } from '@kbn/es-query'; import type { MaintenanceWindowClientContext } from '../../../../../common'; import type { MaintenanceWindow } from '../../types'; import { @@ -45,7 +46,7 @@ async function updateWithOCC( ): Promise { const { savedObjectsClient, getModificationMetadata, logger } = context; const { id, data } = params; - const { title, enabled, duration, rRule, categoryIds } = data; + const { title, enabled, duration, rRule, categoryIds, scopedQuery } = data; try { updateMaintenanceWindowParamsSchema.validate(params); @@ -53,6 +54,25 @@ async function updateWithOCC( throw Boom.badRequest(`Error validating update maintenance window data - ${error.message}`); } + let scopedQueryWithGeneratedValue = scopedQuery; + try { + if (scopedQuery) { + const dsl = JSON.stringify( + buildEsQuery( + undefined, + [{ query: scopedQuery.kql, language: 'kuery' }], + scopedQuery.filters as Filter[] + ) + ); + scopedQueryWithGeneratedValue = { + ...scopedQuery, + dsl, + }; + } + } catch (error) { + throw Boom.badRequest(`Error validating update maintenance scoped query - ${error.message}`); + } + try { const { attributes, @@ -88,6 +108,9 @@ async function updateWithOCC( ...(title ? { title } : {}), ...(rRule ? { rRule: rRule as MaintenanceWindow['rRule'] } : {}), ...(categoryIds !== undefined ? { categoryIds } : {}), + ...(scopedQueryWithGeneratedValue !== undefined + ? { scopedQuery: scopedQueryWithGeneratedValue } + : {}), ...(typeof duration === 'number' ? { duration } : {}), ...(typeof enabled === 'boolean' ? { enabled } : {}), expirationDate, diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/schemas/maintenance_window_schemas.ts b/x-pack/plugins/alerting/server/application/maintenance_window/schemas/maintenance_window_schemas.ts index 080f5b1bbe676..cf9b69454aa9e 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/schemas/maintenance_window_schemas.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/schemas/maintenance_window_schemas.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { maintenanceWindowStatus, maintenanceWindowCategoryIdTypes } from '../constants'; import { rRuleSchema } from '../../r_rule/schemas'; +import { alertsFilterQuerySchema } from '../../alerts_filter_query/schemas'; export const maintenanceWindowEventSchema = schema.object({ gte: schema.string(), @@ -47,4 +48,5 @@ export const maintenanceWindowSchema = schema.object({ schema.literal(maintenanceWindowStatus.ARCHIVED), ]), categoryIds: maintenanceWindowCategoryIdsSchema, + scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)), }); diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_attributes_to_maintenance_window.ts b/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_attributes_to_maintenance_window.ts index 53996f29694f4..6a2f25ce18dd9 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_attributes_to_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_attributes_to_maintenance_window.ts @@ -40,5 +40,6 @@ export const transformMaintenanceWindowAttributesToMaintenanceWindow = ( eventEndTime, status, ...(attributes.categoryIds !== undefined ? { categoryIds: attributes.categoryIds } : {}), + ...(attributes.scopedQuery !== undefined ? { scopedQuery: attributes.scopedQuery } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_to_maintenance_window_attributes.ts b/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_to_maintenance_window_attributes.ts index 35865f38aa9b2..9d26443bdc07d 100644 --- a/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_to_maintenance_window_attributes.ts +++ b/x-pack/plugins/alerting/server/application/maintenance_window/transforms/transform_maintenance_window_to_maintenance_window_attributes.ts @@ -25,5 +25,8 @@ export const transformMaintenanceWindowToMaintenanceWindowAttributes = ( ...(maintenanceWindow.categoryIds !== undefined ? { categoryIds: maintenanceWindow.categoryIds } : {}), + ...(maintenanceWindow.scopedQuery !== undefined + ? { scopedQuery: maintenanceWindow.scopedQuery } + : {}), }; }; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts index 294945ad82037..996f67448c7f8 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.ts @@ -489,7 +489,7 @@ async function updateRuleAttributesAndParamsInMemory( context, operations, rule: ruleDomain, - ruleActions, + ruleActions: ruleActions as RuleDomain['actions'], // TODO (http-versioning) Remove this cast once we fix injectReferencesIntoActions ruleType, }); diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts index 579f41adb0424..7cbadb6199081 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/action_schemas.ts @@ -7,31 +7,10 @@ import { schema } from '@kbn/config-schema'; import { notifyWhenSchema } from './notify_when_schema'; -import { filterStateStore } from '../constants'; +import { alertsFilterQuerySchema } from '../../alerts_filter_query/schemas'; export const actionParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any())); -const actionAlertsFilterQueryFiltersSchema = schema.arrayOf( - schema.object({ - query: schema.maybe(schema.recordOf(schema.string(), schema.any())), - meta: schema.recordOf(schema.string(), schema.any()), - $state: schema.maybe( - schema.object({ - store: schema.oneOf([ - schema.literal(filterStateStore.APP_STATE), - schema.literal(filterStateStore.GLOBAL_STATE), - ]), - }) - ), - }) -); - -const actionDomainAlertsFilterQuerySchema = schema.object({ - kql: schema.string(), - filters: actionAlertsFilterQueryFiltersSchema, - dsl: schema.maybe(schema.string()), -}); - const actionAlertsFilterTimeFrameSchema = schema.object({ days: schema.arrayOf( schema.oneOf([ @@ -52,7 +31,7 @@ const actionAlertsFilterTimeFrameSchema = schema.object({ }); const actionDomainAlertsFilterSchema = schema.object({ - query: schema.maybe(actionDomainAlertsFilterQuerySchema), + query: schema.maybe(alertsFilterQuerySchema), timeframe: schema.maybe(actionAlertsFilterTimeFrameSchema), }); @@ -76,17 +55,8 @@ export const actionDomainSchema = schema.object({ useAlertDataAsTemplate: schema.maybe(schema.boolean()), }); -/** - * Sanitized (non-domain) action schema, returned by rules clients for other solutions - */ -const actionAlertsFilterQuerySchema = schema.object({ - kql: schema.string(), - filters: actionAlertsFilterQueryFiltersSchema, - dsl: schema.maybe(schema.string()), -}); - export const actionAlertsFilterSchema = schema.object({ - query: schema.maybe(actionAlertsFilterQuerySchema), + query: schema.maybe(alertsFilterQuerySchema), timeframe: schema.maybe(actionAlertsFilterTimeFrameSchema), }); diff --git a/x-pack/plugins/alerting/server/data/alerts_filter_query/constants.ts b/x-pack/plugins/alerting/server/data/alerts_filter_query/constants.ts new file mode 100644 index 0000000000000..bce6890c22f2c --- /dev/null +++ b/x-pack/plugins/alerting/server/data/alerts_filter_query/constants.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export const filterStateStore = { + APP_STATE: 'appState', + GLOBAL_STATE: 'globalState', +} as const; + +export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore]; diff --git a/x-pack/plugins/alerting/server/data/alerts_filter_query/types/alerts_filter_query_attributes.ts b/x-pack/plugins/alerting/server/data/alerts_filter_query/types/alerts_filter_query_attributes.ts new file mode 100644 index 0000000000000..f740d8070abf2 --- /dev/null +++ b/x-pack/plugins/alerting/server/data/alerts_filter_query/types/alerts_filter_query_attributes.ts @@ -0,0 +1,22 @@ +/* + * 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 type { FilterStateStore } from '../constants'; + +export interface AlertsFilterAttributes { + query?: Record; + meta: Record; + $state?: { + store: FilterStateStore; + }; +} + +export interface AlertsFilterQueryAttributes { + kql: string; + filters: AlertsFilterAttributes[]; + dsl?: string; +} diff --git a/x-pack/plugins/alerting/server/data/alerts_filter_query/types/index.ts b/x-pack/plugins/alerting/server/data/alerts_filter_query/types/index.ts new file mode 100644 index 0000000000000..6258f925b6e79 --- /dev/null +++ b/x-pack/plugins/alerting/server/data/alerts_filter_query/types/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { AlertsFilterQueryAttributes } from './alerts_filter_query_attributes'; diff --git a/x-pack/plugins/alerting/server/data/maintenance_window/types/maintenance_window_attributes.ts b/x-pack/plugins/alerting/server/data/maintenance_window/types/maintenance_window_attributes.ts index 91f4e95172551..afb217abaf139 100644 --- a/x-pack/plugins/alerting/server/data/maintenance_window/types/maintenance_window_attributes.ts +++ b/x-pack/plugins/alerting/server/data/maintenance_window/types/maintenance_window_attributes.ts @@ -6,7 +6,8 @@ */ import { RRuleAttributes } from '../../r_rule/types'; -import { MaintenanceWindowCategoryIdTypes } from '../constants'; +import type { MaintenanceWindowCategoryIdTypes } from '../constants'; +import { AlertsFilterQueryAttributes } from '../../alerts_filter_query/types'; export interface MaintenanceWindowEventAttributes { gte: string; @@ -25,4 +26,5 @@ export interface MaintenanceWindowAttributes { createdAt: string; updatedAt: string; categoryIds?: MaintenanceWindowCategoryIdTypes[] | null; + scopedQuery?: AlertsFilterQueryAttributes | null; } diff --git a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts index aa8adda873cde..19a669e6bd33e 100644 --- a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts @@ -6,7 +6,6 @@ */ import type { SavedObjectAttributes } from '@kbn/core/server'; -import { Filter } from '@kbn/es-query'; import { IsoWeekday } from '../../../../common'; import { ruleNotifyWhenAttributes, @@ -16,6 +15,7 @@ import { ruleExecutionStatusWarningReasonAttributes, } from '../constants'; import { RRuleAttributes } from '../../r_rule/types'; +import { AlertsFilterQueryAttributes } from '../../alerts_filter_query/types'; export type RuleNotifyWhenAttributes = typeof ruleNotifyWhenAttributes[keyof typeof ruleNotifyWhenAttributes]; @@ -115,11 +115,7 @@ interface AlertsFilterTimeFrameAttributes { } interface AlertsFilterAttributes { - query?: { - kql: string; - filters: Filter[]; - dsl: string; - }; + query?: AlertsFilterQueryAttributes; timeframe?: AlertsFilterTimeFrameAttributes; } diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerting/server/lib/license_state.ts index c774673934ebf..99be775f7c3c6 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.ts @@ -13,7 +13,7 @@ import { capitalize } from 'lodash'; import { Observable, Subscription } from 'rxjs'; import { LicensingPluginStart } from '@kbn/licensing-plugin/server'; import { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types'; -import { PLUGIN } from '../constants/plugin'; +import { PLUGIN } from '../../common/constants/plugin'; import { getRuleTypeFeatureUsageName } from './get_rule_type_feature_usage_name'; import { RuleType, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/transforms/transform_create_body/v1.ts index 03627c258c5eb..6eaa77fc78624 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/transforms/transform_create_body/v1.ts @@ -16,5 +16,6 @@ export const transformCreateBody = ( duration: createBody.duration, rRule: createBody.r_rule, categoryIds: createBody.category_ids, + scopedQuery: createBody.scoped_query, }; }; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/transforms/transform_update_body/v1.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/transforms/transform_update_body/v1.ts index 9da852bd6ebff..96c4e92d4934e 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/transforms/transform_update_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/transforms/transform_update_body/v1.ts @@ -11,12 +11,21 @@ import { UpdateMaintenanceWindowParams } from '../../../../../../application/mai export const transformUpdateBody = ( updateBody: UpdateMaintenanceWindowRequestBodyV1 ): UpdateMaintenanceWindowParams['data'] => { - const { title, enabled, duration, r_rule: rRule, category_ids: categoryIds } = updateBody; + const { + title, + enabled, + duration, + r_rule: rRule, + category_ids: categoryIds, + scoped_query: scopedQuery, + } = updateBody; + return { ...(title !== undefined ? { title } : {}), ...(enabled !== undefined ? { enabled } : {}), ...(duration !== undefined ? { duration } : {}), ...(rRule !== undefined ? { rRule } : {}), ...(categoryIds !== undefined ? { categoryIds } : {}), + ...(scopedQuery !== undefined ? { scopedQuery } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/transforms/transform_maintenance_window_to_response/v1.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/transforms/transform_maintenance_window_to_response/v1.ts index e6e0fb9a6aa42..2d21576773c29 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/transforms/transform_maintenance_window_to_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/transforms/transform_maintenance_window_to_response/v1.ts @@ -29,5 +29,8 @@ export const transformMaintenanceWindowToResponse = ( ...(maintenanceWindow.categoryIds !== undefined ? { category_ids: maintenanceWindow.categoryIds } : {}), + ...(maintenanceWindow.scopedQuery !== undefined + ? { scoped_query: maintenanceWindow.scopedQuery } + : {}), }; }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts index cf0e2a3dcf6a7..05a16cf992848 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/create_maintenance_window.ts @@ -25,6 +25,32 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider tzid: 'UTC', freq: 2, // weekly }, + scoped_query: { + kql: "_id: '1234'", + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + key: 'kibana.alert.action_group', + field: 'kibana.alert.action_group', + params: { + query: 'test', + }, + type: 'phrase', + }, + $state: { + store: 'appState', + }, + query: { + match_phrase: { + 'kibana.alert.action_group': 'test', + }, + }, + }, + ], + }, }; afterEach(() => objectRemover.removeAll()); @@ -69,6 +95,7 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider expect(response.body.r_rule.dtstart).to.eql(createParams.r_rule.dtstart); expect(response.body.events.length).to.be.greaterThan(0); expect(response.body.status).to.eql('running'); + expect(response.body.scoped_query.kql).to.eql("_id: '1234'"); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -102,5 +129,19 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider }) .expect(400); }); + + it('should throw if creating maintenance window with invalid scoped query', async () => { + await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + ...createParams, + scoped_query: { + kql: 'invalid_kql:', + filters: [], + }, + }) + .expect(400); + }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts index c5d38a1c2a056..8d26f316cb840 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/maintenance_window/update_maintenance_window.ts @@ -16,6 +16,33 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const scopedQuery = { + kql: "_id: '1234'", + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + key: 'kibana.alert.action_group', + field: 'kibana.alert.action_group', + params: { + query: 'test', + }, + type: 'phrase', + }, + $state: { + store: 'appState', + }, + query: { + match_phrase: { + 'kibana.alert.action_group': 'test', + }, + }, + }, + ], + }; + describe('updateMaintenanceWindow', () => { const objectRemover = new ObjectRemover(supertest); const createParams = { @@ -26,6 +53,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider tzid: 'UTC', freq: 2, // weekly }, + scoped_query: scopedQuery, }; afterEach(() => objectRemover.removeAll()); @@ -82,6 +110,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider expect(response.body.r_rule.dtstart).to.eql(createParams.r_rule.dtstart); expect(response.body.events.length).to.be.greaterThan(0); expect(response.body.status).to.eql('running'); + expect(response.body.scoped_query.kql).to.eql("_id: '1234'"); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -156,6 +185,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider until: moment.utc().add(1, 'week').toISOString(), }, category_ids: ['management'], + scoped_query: scopedQuery, }) .expect(200); @@ -183,6 +213,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider ...createParams, r_rule: updatedRRule, category_ids: null, + scoped_query: null, }) .expect(200); @@ -194,6 +225,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider expect(response.body.data[0].id).to.eql(createdMaintenanceWindow.id); expect(response.body.data[0].r_rule).to.eql(updatedRRule); expect(response.body.data[0].category_ids).to.eql(null); + expect(response.body.data[0].scoped_query).to.eql(null); }); it('should throw if updating maintenance window with invalid category ids', async () => { @@ -230,5 +262,46 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider .send({ category_ids: ['something-else'] }) .expect(400); }); + + it('should throw if updating maintenance window with invalid scoped query', async () => { + const { body: createdMaintenanceWindow } = await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`) + .set('kbn-xsrf', 'foo') + .send({ + title: 'test-maintenance-window', + duration: 60 * 60 * 1000, // 1 hr + r_rule: { + dtstart: new Date().toISOString(), + tzid: 'UTC', + freq: 2, // weekly + count: 1, + }, + scoped_query: scopedQuery, + }) + .expect(200); + + objectRemover.add( + 'space1', + createdMaintenanceWindow.id, + 'rules/maintenance_window', + 'alerting', + true + ); + + await supertest + .post( + `${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window/${ + createdMaintenanceWindow.id + }` + ) + .set('kbn-xsrf', 'foo') + .send({ + scoped_query: { + kql: 'invalid_kql:', + filters: [], + }, + }) + .expect(400); + }); }); }