Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,7 @@
"@kbn/workflows-ui": "link:src/platform/packages/shared/kbn-workflows-ui",
"@kbn/workplace-ai-app": "link:x-pack/solutions/workplaceai/plugins/workplace_ai_app",
"@kbn/xstate-utils": "link:src/platform/packages/shared/kbn-xstate-utils",
"@kbn/yaml-rule-editor": "link:x-pack/platform/packages/shared/response-ops/yaml-rule-editor",
"@kbn/zod": "link:src/platform/packages/shared/kbn-zod",
"@kbn/zod-helpers": "link:src/platform/packages/shared/kbn-zod-helpers",
"@langchain/aws": "0.1.15",
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -2508,6 +2508,8 @@
"@kbn/workspaces/*": ["src/platform/packages/shared/kbn-workspaces/*"],
"@kbn/xstate-utils": ["src/platform/packages/shared/kbn-xstate-utils"],
"@kbn/xstate-utils/*": ["src/platform/packages/shared/kbn-xstate-utils/*"],
"@kbn/yaml-rule-editor": ["x-pack/platform/packages/shared/response-ops/yaml-rule-editor"],
"@kbn/yaml-rule-editor/*": ["x-pack/platform/packages/shared/response-ops/yaml-rule-editor/*"],
"@kbn/yarn-lock-validator": ["packages/kbn-yarn-lock-validator"],
"@kbn/yarn-lock-validator/*": ["packages/kbn-yarn-lock-validator/*"],
"@kbn/zod": ["src/platform/packages/shared/kbn-zod"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export * from './rule_data_schema';
export { validateDuration, validateEsqlQuery } from './validation';
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,83 @@
* 2.0.
*/

import { Parser } from '@kbn/esql-language';
import { z } from '@kbn/zod';

const DURATION_RE = /^(\d+)(ms|s|m|h|d|w)$/;
import { validateDuration, validateEsqlQuery } from './validation';

const durationSchema = z.string().superRefine((value, ctx) => {
if (!DURATION_RE.test(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid duration "${value}". Expected format like "5m", "1h", "30s", "250ms"`,
});
const error = validateDuration(value);
if (error) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
}
});

const withEsqlValidation = (schema: z.ZodString) =>
schema.superRefine((query, ctx) => {
const errors = Parser.parseErrors(query);
if (errors.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid ES|QL query: ${errors[0].message}`,
});
const esqlQuerySchema = z
.string()
.min(1)
.max(10000)
.superRefine((value, ctx) => {
const error = validateEsqlQuery(value);
if (error) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
}
});

const scheduleSchema = z
.object({
custom: durationSchema,
custom: durationSchema.describe('Rule execution interval (e.g. 1m, 5m).'),
})
.strict();
.strict()
.describe('Schedule configuration for the rule.');

export const createRuleDataSchema = z
.object({
name: z.string().min(1).max(64),
tags: z.array(z.string().max(64)).max(100).default([]),
name: z.string().min(1).max(64).describe('Human-readable rule name.'),
tags: z
.array(z.string().max(64).describe('Rule tag.'))
.max(100)
.default([])
.describe('Tags attached to the rule.'),
schedule: scheduleSchema,
enabled: z.boolean().default(true),
query: withEsqlValidation(z.string().min(1).max(10000)),
timeField: z.string().min(1).max(128).default('@timestamp'),
lookbackWindow: durationSchema,
groupingKey: z.array(z.string()).max(16).default([]),
enabled: z.boolean().default(true).describe('Whether the rule is enabled.'),
query: esqlQuerySchema.describe('ES|QL query text to execute.'),
timeField: z
.string()
.min(1)
.max(128)
.default('@timestamp')
.describe('Time field to apply the lookback window to.'),
lookbackWindow: durationSchema.describe('Lookback window for the query (e.g. 5m, 1h).'),
groupingKey: z
.array(z.string())
.max(16)
.default([])
.describe('Fields to group alert events by.'),
})
.strip();

export type CreateRuleData = z.infer<typeof createRuleDataSchema>;

export const updateRuleDataSchema = z
.object({
name: z.string().min(1).optional(),
tags: z.array(z.string()).optional(),
name: z.string().min(1).max(64).optional().describe('Human-readable rule name.'),
tags: z.array(z.string().max(64)).max(100).optional().describe('Tags attached to the rule.'),
schedule: scheduleSchema.optional(),
enabled: z.boolean().optional(),
query: withEsqlValidation(z.string().min(1)).optional(),
timeField: z.string().min(1).optional(),
lookbackWindow: durationSchema.optional(),
groupingKey: z.array(z.string()).optional(),
enabled: z.boolean().optional().describe('Whether the rule is enabled.'),
query: esqlQuerySchema.optional().describe('ES|QL query text to execute.'),
timeField: z
.string()
.min(1)
.max(128)
.optional()
.describe('Time field to apply the lookback window to.'),
lookbackWindow: durationSchema
.optional()
.describe('Lookback window for the query (e.g. 5m, 1h).'),
groupingKey: z
.array(z.string())
.max(16)
.optional()
.describe('Fields to group alert events by.'),
})
.strip();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { Parser } from '@kbn/esql-language';

const DURATION_RE = /^(\d+)(ms|s|m|h|d|w)$/;

/**
* Validate a duration string format (e.g., "5m", "1h", "30s", "250ms")
* @returns Error message if invalid, undefined if valid
*/
export function validateDuration(value: string): string | void {
if (!DURATION_RE.test(value)) {
return `Invalid duration "${value}". Expected format like "5m", "1h", "30s", "250ms"`;
}
}

/**
* Validate an ES|QL query string
* @returns Error message if invalid, undefined if valid
*/
export function validateEsqlQuery(query: string): string | void {
const errors = Parser.parseErrors(query);
if (errors.length > 0) {
return `Invalid ES|QL query: ${errors[0].message}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/yaml-rule-editor

YAML editor for alerting v2 rules.
Original file line number Diff line number Diff line change
@@ -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 * from './src';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../../../../..',
roots: ['<rootDir>/x-pack/platform/packages/shared/response-ops/yaml-rule-editor'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "shared-browser",
"id": "@kbn/yaml-rule-editor",
"owner": "@elastic/response-ops",
"group": "platform",
"visibility": "shared"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@kbn/yaml-rule-editor",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0",
"sideEffects": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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 { YamlRuleEditor } from './yaml_rule_editor';
export { DEFAULT_ESQL_PROPERTY_NAMES } from './types';
export type { YamlRuleEditorProps, QueryContext, SchemaPropertyInfo } from './types';
Loading