diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 106f59419bf7e..12f408096dd8a 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -6,7 +6,7 @@ pageLoadAssetSize: aiAssistantManagementSelection: 13590 aiops: 15227 alerting: 22371 - alertingVTwo: 361715 + alertingVTwo: 362170 apm: 38573 apmSourcesAccess: 2278 automaticImport: 12162 diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index bbf2619e54bd0..c56b8d6c3a46d 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -62,7 +62,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ad_hoc_run_params": "9c372f2a8f8b468e9b699a6df633c7f14fab7f13216c9ec160813e75bae56098", "alert": "119624b6025ea6794d2c33e2b41c2e4730d10446430b285691f7638ee6787af5", "alerting_notification_policy": "81fc65ed6652cd1196f83b4222f227e8c7a3f646a6e044f63a2d82f12dacbfb0", - "alerting_rule": "9e26ddcbca3edb3803dcfe3d6bf908341441fb32c67005cab71ae0f7b9841387", + "alerting_rule": "8f80d561d2b3caf07925be4a5fff52d8433ca3f3f204723f6f3e9e32dbce7f42", "alerting_rule_template": "a26521005d8a51af336ec95a2097c4bd073980c050e3c675cec3851acff78fd9", "api_key_pending_invalidation": "b5a0fe007bff147bbb0ef7d0393c976f777ccb470359090d79890a769baf3c68", "api_key_to_invalidate": "5add5ee737ccc61cc16bbf68423d634d1354971f20926b5ff465a2a853d1723a", @@ -303,7 +303,7 @@ describe('checking migration metadata changes on all registered SO types', () => "alerting_rule|global: e78adb1490c02adb4c705491c87e08332c0f668e", "alerting_rule|mappings: 05a17ab7488d3b86ec25c724f21a19cd7ebb9778", "alerting_rule|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", - "alerting_rule|10.1.0: 8b54e0f9a45fff3fbb39dd35dde15784674293f7dfb199bc10218f3658266207", + "alerting_rule|10.1.0: 7e2e621ff8a85dd04d0b8e374a9a8b85ce23ba968dfbacef9dfd23a003dc4afb", "======================================================================================", "alerting_rule_template|global: a8ee387a4bc794ff6450017a92742b39b79e0446", "alerting_rule_template|mappings: eccf889027b5ea2d292c1bf0f9280348deaec0ef", diff --git a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.test.ts b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.test.ts index de164de037e2a..6ee4aa81646f6 100644 --- a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.test.ts +++ b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.test.ts @@ -11,6 +11,11 @@ const validCreateData = { kind: 'alert', metadata: { name: 'test rule' }, schedule: { every: '5m' }, + evaluation: { query: { base: 'FROM logs-* | LIMIT 1' } }, +}; + +const validCreateDataWithCondition = { + ...validCreateData, evaluation: { query: { base: 'FROM logs-* | LIMIT 1', condition: 'count > 0' } }, }; @@ -19,6 +24,18 @@ describe('createRuleDataSchema', () => { it('accepts a minimal valid payload and applies defaults', () => { const result = createRuleDataSchema.parse(validCreateData); + expect(result).toEqual({ + kind: 'alert', + metadata: { name: 'test rule' }, + time_field: '@timestamp', + schedule: { every: '5m' }, + evaluation: { query: { base: 'FROM logs-* | LIMIT 1' } }, + }); + }); + + it('accepts a minimal valid payload with condition', () => { + const result = createRuleDataSchema.parse(validCreateDataWithCondition); + expect(result).toEqual({ kind: 'alert', metadata: { name: 'test rule' }, @@ -30,7 +47,7 @@ describe('createRuleDataSchema', () => { it('accepts a full payload with all optional fields', () => { const result = createRuleDataSchema.parse({ - ...validCreateData, + ...validCreateDataWithCondition, metadata: { name: 'test rule', owner: 'team-a', labels: ['label-1', 'label-2'] }, time_field: 'event.created', schedule: { every: '5m', lookback: '10m' }, @@ -153,7 +170,7 @@ describe('createRuleDataSchema', () => { it('rejects an empty query', () => { const result = createRuleDataSchema.safeParse({ ...validCreateData, - evaluation: { query: { base: '', condition: 'count > 0' } }, + evaluation: { query: { base: '' } }, }); expect(result.success).toBe(false); }); @@ -161,7 +178,63 @@ describe('createRuleDataSchema', () => { it('rejects an invalid ES|QL query', () => { const result = createRuleDataSchema.safeParse({ ...validCreateData, - evaluation: { query: { base: 'FROM |', condition: 'count > 0' } }, + evaluation: { query: { base: 'FROM |' } }, + }); + expect(result.success).toBe(false); + }); + }); + + describe('evaluation.query.condition', () => { + it('accepts an alert rule without condition', () => { + const result = createRuleDataSchema.safeParse(validCreateData); + expect(result.success).toBe(true); + expect(result.data?.evaluation.query.condition).toBeUndefined(); + }); + + it('accepts an alert rule with condition', () => { + const result = createRuleDataSchema.safeParse(validCreateDataWithCondition); + expect(result.success).toBe(true); + expect(result.data?.evaluation.query.condition).toBe('count > 0'); + }); + + it('accepts a signal rule without condition', () => { + const result = createRuleDataSchema.safeParse({ + ...validCreateData, + kind: 'signal', + }); + expect(result.success).toBe(true); + }); + + it('rejects a signal rule with condition', () => { + const result = createRuleDataSchema.safeParse({ + ...validCreateDataWithCondition, + kind: 'signal', + }); + expect(result.success).toBe(false); + expect(result.error?.issues[0].path).toEqual(['evaluation', 'query', 'condition']); + }); + + it('requires condition when no_data is configured', () => { + const result = createRuleDataSchema.safeParse({ + ...validCreateData, + no_data: { behavior: 'no_data', timeframe: '10m' }, + }); + expect(result.success).toBe(false); + expect(result.error?.issues[0].path).toEqual(['evaluation', 'query', 'condition']); + }); + + it('accepts no_data with condition present', () => { + const result = createRuleDataSchema.safeParse({ + ...validCreateDataWithCondition, + no_data: { behavior: 'no_data', timeframe: '10m' }, + }); + expect(result.success).toBe(true); + }); + + it('rejects an empty condition string', () => { + const result = createRuleDataSchema.safeParse({ + ...validCreateData, + evaluation: { query: { base: 'FROM logs-* | LIMIT 1', condition: '' } }, }); expect(result.success).toBe(false); }); diff --git a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.ts b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.ts index 2e2dadaf792bb..cf7a6332eb8c2 100644 --- a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.ts +++ b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.ts @@ -62,7 +62,12 @@ const scheduleSchema = z const evaluationQuerySchema = z .object({ base: esqlQuerySchema.describe('Base ES|QL query.'), - condition: z.string().min(1).max(5000).describe('Trigger condition (WHERE clause).'), + condition: z + .string() + .min(1) + .max(5000) + .optional() + .describe('Trigger condition (WHERE clause). Required when no_data is configured.'), }) .strict(); @@ -180,18 +185,17 @@ export const createRuleDataSchema = z notification_policies: z.array(notificationPolicyRefSchema).optional(), }) .strip() - /** - * - * The `.refine` method adds a custom validation to the schema. - * In this case, it enforces that the `state_transition` property is only allowed when `kind` is "alert". - * The predicate `data.kind === 'alert' || data.state_transition == null` means: - * - If the rule kind is "alert", `state_transition` may be present (or absent). - * - For any other `kind`, `state_transition` must be `null` or `undefined`. - * If validation fails, the specified error message will be associated with the `state_transition` field. - */ .refine((data) => data.kind === 'alert' || data.state_transition == null, { message: 'state_transition is only allowed when kind is "alert".', path: ['state_transition'], + }) + .refine((data) => data.kind === 'alert' || data.evaluation.query.condition == null, { + message: 'evaluation.query.condition is only allowed when kind is "alert".', + path: ['evaluation', 'query', 'condition'], + }) + .refine((data) => !data.no_data || data.evaluation.query.condition != null, { + message: 'evaluation.query.condition is required when no_data is configured.', + path: ['evaluation', 'query', 'condition'], }); export type CreateRuleData = z.infer; diff --git a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_response.ts b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_response.ts index 2a7378d47f2d0..40d506b1e31e5 100644 --- a/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_response.ts +++ b/x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_response.ts @@ -29,7 +29,7 @@ export interface RuleResponse { evaluation: { query: { base: string; - condition: string; + condition?: string; }; }; recovery_policy?: { diff --git a/x-pack/platform/packages/shared/response-ops/yaml-rule-editor/src/types.ts b/x-pack/platform/packages/shared/response-ops/yaml-rule-editor/src/types.ts index caeb3c01c6451..a733973c1798a 100644 --- a/x-pack/platform/packages/shared/response-ops/yaml-rule-editor/src/types.ts +++ b/x-pack/platform/packages/shared/response-ops/yaml-rule-editor/src/types.ts @@ -59,9 +59,12 @@ export interface CompletionContext { } /** - * Default property names that should be treated as ES|QL queries + * YAML property names whose values contain ES|QL queries. + * The editor enables ES|QL syntax highlighting and auto-completion for these fields. + * + * TODO: with zod4 upgrade we can add a metadata tag to the schema to mark these fields as ES|QL queries. */ -export const DEFAULT_ESQL_PROPERTY_NAMES = ['query']; +export const DEFAULT_ESQL_PROPERTY_NAMES = ['base']; /** * Props for the YamlRuleEditor component diff --git a/x-pack/platform/plugins/shared/alerting_v2/public/components/create_rule_page.tsx b/x-pack/platform/plugins/shared/alerting_v2/public/components/create_rule_page.tsx index ad44422fbc199..a9961f428905a 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/public/components/create_rule_page.tsx +++ b/x-pack/platform/plugins/shared/alerting_v2/public/components/create_rule_page.tsx @@ -139,8 +139,9 @@ export const CreateRulePage = () => { evaluation: { query: { base: rule.evaluation?.query?.base ?? DEFAULT_RULE_VALUES.evaluation.query.base, - condition: - rule.evaluation?.query?.condition ?? DEFAULT_RULE_VALUES.evaluation.query.condition, + ...(rule.evaluation?.query?.condition != null + ? { condition: rule.evaluation.query.condition } + : {}), }, }, recovery_policy: rule.recovery_policy, diff --git a/x-pack/platform/plugins/shared/alerting_v2/server/saved_objects/schemas/rule_saved_object_attributes/v1.ts b/x-pack/platform/plugins/shared/alerting_v2/server/saved_objects/schemas/rule_saved_object_attributes/v1.ts index 600ef5f6e80f9..4c669eae47fb0 100644 --- a/x-pack/platform/plugins/shared/alerting_v2/server/saved_objects/schemas/rule_saved_object_attributes/v1.ts +++ b/x-pack/platform/plugins/shared/alerting_v2/server/saved_objects/schemas/rule_saved_object_attributes/v1.ts @@ -32,7 +32,7 @@ export const ruleSavedObjectAttributesSchema = schema.object({ evaluation: schema.object({ query: schema.object({ base: schema.string(), - condition: schema.string(), + condition: schema.maybe(schema.string()), }), }), recovery_policy: schema.maybe(