[ResponseOps][Rules] Move the params of es query and index threshold rule types (#210197)

Connected with https://github.com/elastic/kibana/issues/195188

## Summary

- Moved params of es query rule type to
`@kbn/response-ops-rule-params/es_query` package
- Moved params of index threshold rule type to
`@kbn/response-ops-rule-params/index_threshold` package

The following constants for the es query rule type have been duplicated:
    - MAX_SELECTABLE_SOURCE_FIELDS
    - MAX_SELECTABLE_GROUP_BY_TERMS
    - ES_QUERY_MAX_HITS_PER_EXECUTION
This commit is contained in:
Georgiana-Andreea Onoleață 2025-02-18 09:58:37 +02:00 committed by GitHub
parent 3ce4cf575b
commit 71254c8ee5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 576 additions and 556 deletions

View file

@ -12,6 +12,20 @@
*/
export const MAX_DATA_VIEW_FIELD_DESCRIPTION_LENGTH = 300;
export const MAX_SELECTABLE_SOURCE_FIELDS = 5;
export const MAX_SELECTABLE_GROUP_BY_TERMS = 4;
export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000;
export const MAX_GROUPS = 1000;
export enum Comparator {
GT = '>',
LT = '<',
GT_OR_EQ = '>=',
LT_OR_EQ = '<=',
BETWEEN = 'between',
NOT_BETWEEN = 'notBetween',
}
/**
* All runtime field types.
* @public

View file

@ -9,3 +9,5 @@
export * from './search_configuration_schema';
export { dataViewSpecSchema } from './data_view_spec_schema';
export { MAX_GROUPS } from './constants';
export { ComparatorFns } from './utils';

View file

@ -10,8 +10,19 @@
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { buildEsQuery as kbnBuildEsQuery } from '@kbn/es-query';
import {
buildEsQuery as kbnBuildEsQuery,
toElasticsearchQuery,
fromKueryExpression,
} from '@kbn/es-query';
import { Comparator } from './constants';
export type ComparatorFn = (value: number, threshold: number[]) => boolean;
const TimeWindowUnits = new Set(['s', 'm', 'h', 'd']);
const AggTypes = new Set(['count', 'avg', 'min', 'max', 'sum']);
export const betweenComparators = new Set(['between', 'notBetween']);
export const jobsSelectionSchema = schema.object(
{
jobIds: schema.arrayOf(schema.string(), { defaultValue: [] }),
@ -80,3 +91,102 @@ export type TimeUnitChar = 's' | 'm' | 'h' | 'd';
export enum LEGACY_COMPARATORS {
OUTSIDE_RANGE = 'outside',
}
export function validateTimeWindowUnits(timeWindowUnit: string): string | undefined {
if (TimeWindowUnits.has(timeWindowUnit)) {
return;
}
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.invalidTimeWindowUnitsErrorMessage',
{
defaultMessage: 'invalid timeWindowUnit: "{timeWindowUnit}"',
values: {
timeWindowUnit,
},
}
);
}
export function validateAggType(aggType: string): string | undefined {
if (AggTypes.has(aggType)) {
return;
}
return i18n.translate(
'xpack.responseOps.ruleParams.data.coreQueryParams.invalidAggTypeErrorMessage',
{
defaultMessage: 'invalid aggType: "{aggType}"',
values: {
aggType,
},
}
);
}
export function validateGroupBy(groupBy: string): string | undefined {
if (groupBy === 'all' || groupBy === 'top') {
return;
}
return i18n.translate('xpack.responseOps.ruleParams.coreQueryParams.invalidGroupByErrorMessage', {
defaultMessage: 'invalid groupBy: "{groupBy}"',
values: {
groupBy,
},
});
}
export const ComparatorFns = new Map<Comparator, ComparatorFn>([
[Comparator.LT, (value: number, threshold: number[]) => value < threshold[0]],
[Comparator.LT_OR_EQ, (value: number, threshold: number[]) => value <= threshold[0]],
[Comparator.GT_OR_EQ, (value: number, threshold: number[]) => value >= threshold[0]],
[Comparator.GT, (value: number, threshold: number[]) => value > threshold[0]],
[
Comparator.BETWEEN,
(value: number, threshold: number[]) => value >= threshold[0] && value <= threshold[1],
],
[
Comparator.NOT_BETWEEN,
(value: number, threshold: number[]) => value < threshold[0] || value > threshold[1],
],
]);
export const getComparatorSchemaType = (validate: (comparator: Comparator) => string | void) =>
schema.oneOf(
[
schema.literal(Comparator.GT),
schema.literal(Comparator.LT),
schema.literal(Comparator.GT_OR_EQ),
schema.literal(Comparator.LT_OR_EQ),
schema.literal(Comparator.BETWEEN),
schema.literal(Comparator.NOT_BETWEEN),
],
{ validate }
);
export const ComparatorFnNames = new Set(ComparatorFns.keys());
export function validateKuery(query: string): string | undefined {
try {
toElasticsearchQuery(fromKueryExpression(query));
} catch (e) {
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.invalidKQLQueryErrorMessage',
{
defaultMessage: 'Filter query is invalid.',
}
);
}
}
export function validateComparator(comparator: Comparator): string | undefined {
if (ComparatorFnNames.has(comparator)) return;
return i18n.translate('xpack.responseOps.ruleParams.invalidComparatorErrorMessage', {
defaultMessage: 'invalid thresholdComparator specified: {comparator}',
values: {
comparator,
},
});
}

View file

@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export { EsQueryRuleParamsSchema } from './latest';
export { EsQueryRuleParamsSchema as EsQueryRuleParamsSchemaV1 } from './v1';
export type { EsQueryRuleParams } from './latest';
export type { EsQueryRuleParams as EsQueryRuleParamsV1 } from './latest';

View file

@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export * from './v1';

View file

@ -0,0 +1,217 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import {
validateTimeWindowUnits,
validateAggType,
validateGroupBy,
getComparatorSchemaType,
betweenComparators,
validateComparator,
} from '../common/utils';
import {
MAX_SELECTABLE_SOURCE_FIELDS,
MAX_SELECTABLE_GROUP_BY_TERMS,
ES_QUERY_MAX_HITS_PER_EXECUTION,
MAX_GROUPS,
Comparator,
} from '../common/constants';
const EsQueryRuleParamsSchemaProperties = {
size: schema.number({ min: 0, max: ES_QUERY_MAX_HITS_PER_EXECUTION }),
timeWindowSize: schema.number({ min: 1 }),
excludeHitsFromPreviousRun: schema.boolean({ defaultValue: true }),
timeWindowUnit: schema.string({ validate: validateTimeWindowUnits }),
threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }),
thresholdComparator: getComparatorSchemaType(validateComparator),
// aggregation type
aggType: schema.string({ validate: validateAggType, defaultValue: 'count' }),
// aggregation field
aggField: schema.maybe(schema.string({ minLength: 1 })),
// how to group
groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }),
// field to group on (for groupBy: top)
termField: schema.maybe(
schema.oneOf([
schema.string({ minLength: 1 }),
schema.arrayOf(schema.string(), { minSize: 2, maxSize: MAX_SELECTABLE_GROUP_BY_TERMS }),
])
),
// limit on number of groups returned
termSize: schema.maybe(schema.number({ min: 1 })),
searchType: schema.oneOf(
[schema.literal('searchSource'), schema.literal('esQuery'), schema.literal('esqlQuery')],
{
defaultValue: 'esQuery',
}
),
timeField: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.string({ minLength: 1 }),
schema.maybe(schema.string({ minLength: 1 }))
),
// searchSource rule param only
searchConfiguration: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('searchSource'),
schema.object({}, { unknowns: 'allow' }),
schema.never()
),
// esQuery rule params only
esQuery: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.string({ minLength: 1 }),
schema.never()
),
index: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }),
schema.never()
),
// esqlQuery rule params only
esqlQuery: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esqlQuery'),
schema.object({ esql: schema.string({ minLength: 1 }) }),
schema.never()
),
sourceFields: schema.maybe(
schema.arrayOf(
schema.object({
label: schema.string(),
searchPath: schema.string(),
}),
{
maxSize: MAX_SELECTABLE_SOURCE_FIELDS,
}
)
),
};
// rule type parameters
export type EsQueryRuleParams = TypeOf<typeof EsQueryRuleParamsSchema>;
function isSearchSourceRule(searchType: EsQueryRuleParams['searchType']) {
return searchType === 'searchSource';
}
function isEsqlQueryRule(searchType: EsQueryRuleParams['searchType']) {
return searchType === 'esqlQuery';
}
// using direct type not allowed, circular reference, so body is typed to any
function validateParams(anyParams: unknown): string | undefined {
const {
esQuery,
thresholdComparator,
threshold,
searchType,
aggType,
aggField,
groupBy,
termField,
termSize,
} = anyParams as EsQueryRuleParams;
if (betweenComparators.has(thresholdComparator) && threshold.length === 1) {
return i18n.translate('responseOps.ruleParams.esQuery.invalidThreshold2ErrorMessage', {
defaultMessage:
'[threshold]: must have two elements for the "{thresholdComparator}" comparator',
values: {
thresholdComparator,
},
});
}
if (aggType !== 'count' && !aggField) {
return i18n.translate('responseOps.ruleParams.esQuery.aggTypeRequiredErrorMessage', {
defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"',
values: {
aggType,
},
});
}
// check grouping
if (groupBy === 'top') {
if (termField == null) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.termFieldRequiredErrorMessage', {
defaultMessage: '[termField]: termField required when [groupBy] is top',
});
}
if (termSize == null) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.termSizeRequiredErrorMessage', {
defaultMessage: '[termSize]: termSize required when [groupBy] is top',
});
}
if (termSize > MAX_GROUPS) {
return i18n.translate(
'xpack.responseOps.ruleParams.esQuery.invalidTermSizeMaximumErrorMessage',
{
defaultMessage: '[termSize]: must be less than or equal to {maxGroups}',
values: {
maxGroups: MAX_GROUPS,
},
}
);
}
}
if (isSearchSourceRule(searchType)) {
return;
}
if (isEsqlQueryRule(searchType)) {
const { timeField } = anyParams as EsQueryRuleParams;
if (!timeField) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.esqlTimeFieldErrorMessage', {
defaultMessage: '[timeField]: is required',
});
}
if (thresholdComparator !== Comparator.GT) {
return i18n.translate(
'xpack.responseOps.ruleParams.esQuery.esqlThresholdComparatorErrorMessage',
{
defaultMessage: '[thresholdComparator]: is required to be greater than',
}
);
}
if (threshold && threshold[0] !== 0) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.esqlThresholdErrorMessage', {
defaultMessage: '[threshold]: is required to be 0',
});
}
return;
}
try {
const parsedQuery = JSON.parse(esQuery);
if (parsedQuery && !parsedQuery.query) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.missingEsQueryErrorMessage', {
defaultMessage: '[esQuery]: must contain "query"',
});
}
} catch (err) {
return i18n.translate('xpack.responseOps.ruleParams.esQuery.invalidEsQueryErrorMessage', {
defaultMessage: '[esQuery]: must be valid JSON',
});
}
}
export const EsQueryRuleParamsSchema = schema.object(EsQueryRuleParamsSchemaProperties, {
validate: validateParams,
});

View file

@ -0,0 +1,19 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export { ParamsSchema } from './latest';
export { ParamsSchema as ParamsSchemaV1 } from './latest';
export { type Params } from './latest';
export { type Params as ParamsV1 } from './latest';
export type { CoreQueryParams } from './latest';
export type { CoreQueryParams as CoreQueryParamsV1 } from './latest';
export { CoreQueryParamsSchemaProperties } from './latest';
export { validateCoreQueryBody } from './latest';

View file

@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export * from './v1';

View file

@ -0,0 +1,134 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { MAX_GROUPS } from '../common/constants';
import {
validateAggType,
validateGroupBy,
validateKuery,
validateTimeWindowUnits,
getComparatorSchemaType,
betweenComparators,
validateComparator,
} from '../common/utils';
export type Params = TypeOf<typeof ParamsSchema>;
export const CoreQueryParamsSchemaProperties = {
// name of the indices to search
index: schema.oneOf([
schema.string({ minLength: 1 }),
schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }),
]),
// field in index used for date/time
timeField: schema.string({ minLength: 1 }),
// aggregation type
aggType: schema.string({ validate: validateAggType, defaultValue: 'count' }),
// aggregation field
aggField: schema.maybe(schema.string({ minLength: 1 })),
// how to group
groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }),
// field to group on (for groupBy: top)
termField: schema.maybe(schema.string({ minLength: 1 })),
// filter field
filterKuery: schema.maybe(schema.string({ validate: validateKuery })),
// limit on number of groups returned
termSize: schema.maybe(schema.number({ min: 1 })),
// size of time window for date range aggregations
timeWindowSize: schema.number({ min: 1 }),
// units of time window for date range aggregations
timeWindowUnit: schema.string({ validate: validateTimeWindowUnits }),
};
export const CoreQueryParamsSchema = schema.object(CoreQueryParamsSchemaProperties);
export type CoreQueryParams = TypeOf<typeof CoreQueryParamsSchema>;
export const ParamsSchema = schema.object(
{
...CoreQueryParamsSchemaProperties,
// the comparison function to use to determine if the threshold as been met
thresholdComparator: getComparatorSchemaType(validateComparator),
// the values to use as the threshold; `between` and `notBetween` require
// two values, the others require one.
threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }),
},
{ validate: validateParams }
);
// using direct type not allowed, circular reference, so body is typed to any
function validateParams(anyParams: unknown): string | undefined {
// validate core query parts, return if it fails validation (returning string)
const coreQueryValidated = validateCoreQueryBody(anyParams);
if (coreQueryValidated) return coreQueryValidated;
const { thresholdComparator, threshold }: Params = anyParams as Params;
if (betweenComparators.has(thresholdComparator) && threshold.length === 1) {
return i18n.translate(
'xpack.responseOps.ruleParams.indexThreshold.invalidThreshold2ErrorMessage',
{
defaultMessage:
'[threshold]: must have two elements for the "{thresholdComparator}" comparator',
values: {
thresholdComparator,
},
}
);
}
}
export function validateCoreQueryBody(anyParams: unknown): string | undefined {
const { aggType, aggField, groupBy, termField, termSize }: CoreQueryParams =
anyParams as CoreQueryParams;
if (aggType !== 'count' && !aggField) {
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.aggTypeRequiredErrorMessage',
{
defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"',
values: {
aggType,
},
}
);
}
// check grouping
if (groupBy === 'top') {
if (termField == null) {
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.termFieldRequiredErrorMessage',
{
defaultMessage: '[termField]: termField required when [groupBy] is top',
}
);
}
if (termSize == null) {
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.termSizeRequiredErrorMessage',
{
defaultMessage: '[termSize]: termSize required when [groupBy] is top',
}
);
}
if (termSize > MAX_GROUPS) {
return i18n.translate(
'xpack.responseOps.ruleParams.coreQueryParams.invalidTermSizeMaximumErrorMessage',
{
defaultMessage: '[termSize]: must be less than or equal to {maxGroups}',
values: {
maxGroups: MAX_GROUPS,
},
}
);
}
}
}

View file

@ -41804,24 +41804,14 @@
"xpack.stackAlerts.esQuery.actionVariableContextThresholdLabel": "Un tableau des valeurs des règles de seuil. Il y a deux valeurs pour les seuils \"Between\" (Intermédiaires) et \"notBetween\" (Non-intermédiaires).",
"xpack.stackAlerts.esQuery.actionVariableContextTitleLabel": "Titre pour l'alerte.",
"xpack.stackAlerts.esQuery.actionVariableContextValueLabel": "Valeur ayant rempli la condition de seuil.",
"xpack.stackAlerts.esQuery.aggTypeRequiredErrorMessage": "[aggField] : doit posséder une valeur lorsque [aggType] est \"{aggType}\"",
"xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription": "Le nombre de documents correspondants {groupCondition}{aggCondition} est {negation}{thresholdComparator} {threshold}",
"xpack.stackAlerts.esQuery.alertTypeContextReasonDescription": "Le décompte de documents est {value} au cours des derniers/dernières {window}{verb}{index}. Signaler quand {comparator} {threshold}.",
"xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle": "règle \"{name}\" : {verb}",
"xpack.stackAlerts.esQuery.alertTypeTitle": "Recherche Elasticsearch",
"xpack.stackAlerts.esQuery.esqlAlertTypeContextConditionsDescription": "Recherche {negation} de documents{groupCondition}",
"xpack.stackAlerts.esQuery.esqlThresholdComparatorErrorMessage": "[thresholdComparator] : doit être plus élevé que",
"xpack.stackAlerts.esQuery.esqlThresholdErrorMessage": "[threshold] :doit correspondre à 0",
"xpack.stackAlerts.esQuery.esqlTimeFieldErrorMessage": "[timeField] : est requis",
"xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "thresholdComparator spécifié non valide : {comparator}",
"xpack.stackAlerts.esQuery.invalidEsQueryErrorMessage": "[esQuery] : doit être au format JSON valide",
"xpack.stackAlerts.esQuery.invalidQueryErrorMessage": "requête spécifiée non valide : \"{query}\" - la requête doit être au format JSON",
"xpack.stackAlerts.esQuery.invalidTermSizeMaximumErrorMessage": "[termSize] : doit être inférieure ou égale à {maxGroups}",
"xpack.stackAlerts.esQuery.invalidThreshold2ErrorMessage": "[threshold] : requiert deux éléments pour le comparateur \"{thresholdComparator}\"",
"xpack.stackAlerts.esQuery.missingEsQueryErrorMessage": "[esQuery] : doit contenir \"query\"",
"xpack.stackAlerts.esQuery.serverless.sizeErrorMessage": "[size] : doit être inférieur ou égal à {maxSize}",
"xpack.stackAlerts.esQuery.termFieldRequiredErrorMessage": "[termField] : termField requis lorsque [groupBy] est Premiers",
"xpack.stackAlerts.esQuery.termSizeRequiredErrorMessage": "[termSize] : termSize requis lorsque [groupBy] est Premiers",
"xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "L'expression contient des erreurs.",
"xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "La règle de requête Elasticsearch '{{rule.name}}' est active : - Valeur : '{{context.value}}' - Conditions remplies : '{{context.conditions}}' sur '{{rule.params.timeWindowSize}}''{{rule.params.timeWindowUnit}}' - Horodatage : '{{context.date}}' - Lien : '{{context.link}}'",
"xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "Alerte lorsque des correspondances sont trouvées au cours de la dernière exécution de la requête.",
@ -41947,7 +41937,6 @@
"xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle": "groupe {group} de l'alerte {name} récupéré",
"xpack.stackAlerts.indexThreshold.alertTypeTitle": "Seuil de l'index",
"xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "thresholdComparator spécifié non valide : {comparator}",
"xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold] : requiert deux éléments pour le comparateur \"{thresholdComparator}\"",
"xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "L'expression contient des erreurs.",
"xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage": "La règle '{{rule.name}}' est active pour le groupe '{{context.group}}' : - Valeur : '{{context.value}}' - Conditions remplies : '{{context.conditions}}' sur '{{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}' - Horodatage : '{{context.date}}'",
"xpack.stackAlerts.threshold.ui.alertType.descriptionText": "Alerte lorsqu'une recherche agrégée atteint le seuil.",
@ -44657,20 +44646,13 @@
"xpack.triggersActionsUI.connectors.home.description": "Connectez des logiciels tiers avec vos données d'alerting.",
"xpack.triggersActionsUI.connectors.home.documentationButtonLabel": "Documentation",
"xpack.triggersActionsUI.connectors.home.logsTabTitle": "Logs",
"xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggField] : doit posséder une valeur lorsque [aggType] est \"{aggType}\"",
"xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart] : est postérieure à [dateEnd]",
"xpack.triggersActionsUI.data.coreQueryParams.formattedFieldErrorMessage": "format {formatName} non valide pour {fieldName} : \"{fieldValue}\"",
"xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval] : doit être spécifié si [dateStart] n'est pas égale à [dateEnd]",
"xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage": "aggType non valide : \"{aggType}\"",
"xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage": "date {date} non valide",
"xpack.triggersActionsUI.data.coreQueryParams.invalidDurationErrorMessage": "durée non valide : \"{duration}\"",
"xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage": "groupBy non valide : \"{groupBy}\"",
"xpack.triggersActionsUI.data.coreQueryParams.invalidKQLQueryErrorMessage": "La requête de filtre n'est pas valide.",
"xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage": "[termSize] : doit être inférieure ou égale à {maxGroups}",
"xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage": "timeWindowUnit non valide : \"{timeWindowUnit}\"",
"xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage": "le nombre calculé d'intervalles {intervals} est supérieur au maximum {maxIntervals}",
"xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage": "[termField] : termField requis lorsque [groupBy] est Premiers",
"xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage": "[termSize] : termSize requis lorsque [groupBy] est Premiers",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "Annuler",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "Supprimer {numIdsToDelete, plural, one {{singleTitle}} other {# {multipleTitle}}}",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.descriptionText": "Vous ne pourrez pas récupérer {numIdsToDelete, plural, one {un {singleTitle} supprimé} other {des {multipleTitle} supprimés}}.",

View file

@ -41657,24 +41657,14 @@
"xpack.stackAlerts.esQuery.actionVariableContextThresholdLabel": "ルールのしきい値の配列。betweenとnotBetweenのしきい値には2つの値があります。",
"xpack.stackAlerts.esQuery.actionVariableContextTitleLabel": "アラートのタイトル。",
"xpack.stackAlerts.esQuery.actionVariableContextValueLabel": "しきい値条件を満たした値。",
"xpack.stackAlerts.esQuery.aggTypeRequiredErrorMessage": "[aggType]が「{aggType}」のときには[aggField]に値が必要です",
"xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription": "一致するドキュメント数{groupCondition}{aggCondition}は{negation}{thresholdComparator} {threshold}です",
"xpack.stackAlerts.esQuery.alertTypeContextReasonDescription": "前回の{window}{verb}{index}ではドキュメント数は{value}です。{comparator} {threshold}のときにアラートを発行します。",
"xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle": "ルール''{name}''{verb}",
"xpack.stackAlerts.esQuery.alertTypeTitle": "Elasticsearch クエリー",
"xpack.stackAlerts.esQuery.esqlAlertTypeContextConditionsDescription": "クエリー{negation}ドキュメント{groupCondition}",
"xpack.stackAlerts.esQuery.esqlThresholdComparatorErrorMessage": "[thresholdComparator]:はそれよりも大きくなければなりません",
"xpack.stackAlerts.esQuery.esqlThresholdErrorMessage": "[threshold]は0でなければなりません",
"xpack.stackAlerts.esQuery.esqlTimeFieldErrorMessage": "[timeField]は必須です",
"xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "無効な thresholdComparator が指定されました:{comparator}",
"xpack.stackAlerts.esQuery.invalidEsQueryErrorMessage": "[esQuery]有効なJSONでなければなりません",
"xpack.stackAlerts.esQuery.invalidQueryErrorMessage": "無効なクエリーが指定されました: \"{query}\" - クエリーはJSONでなければなりません",
"xpack.stackAlerts.esQuery.invalidTermSizeMaximumErrorMessage": "[termSize]{maxGroups}以下でなければなりません。",
"xpack.stackAlerts.esQuery.invalidThreshold2ErrorMessage": "[threshold]:「{thresholdComparator}」比較子の場合には2つの要素が必要です",
"xpack.stackAlerts.esQuery.missingEsQueryErrorMessage": "[esQuery]「query」を含む必要があります",
"xpack.stackAlerts.esQuery.serverless.sizeErrorMessage": "[size]{maxSize}以下でなければなりません",
"xpack.stackAlerts.esQuery.termFieldRequiredErrorMessage": "[termField][groupBy]がトップのときにはtermFieldが必要です",
"xpack.stackAlerts.esQuery.termSizeRequiredErrorMessage": "[termSize][groupBy]がトップのときにはtermSizeが必要です",
"xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。",
"xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "Elasticsearchクエリルール'{{rule.name}}'がアクティブです:- 値:'{{context.value}}' - 満たされた条件:'{{rule.params.timeWindowSize}}''{{rule.params.timeWindowUnit}}'の'{{context.conditions}}' - タイムスタンプ:'{{context.date}}' - リンク:'{{context.link}}'",
"xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "前回のクエリ実行中に一致が見つかったときにアラートを発行します。",
@ -41800,7 +41790,6 @@
"xpack.stackAlerts.indexThreshold.alertTypeRecoveryContextSubjectTitle": "アラート{name}グループ{group}が回復されました",
"xpack.stackAlerts.indexThreshold.alertTypeTitle": "インデックスしきい値",
"xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "無効な thresholdComparator が指定されました:{comparator}",
"xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]:「{thresholdComparator}」比較子の場合には2つの要素が必要です",
"xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。",
"xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage": "グループ'{{context.group}}'のルール'{{rule.name}}'がアクティブです:- 値:'{{context.value}}' - 満たされた条件:'{{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}'の'{{context.conditions}}' - タイムスタンプ:'{{context.date}}'",
"xpack.stackAlerts.threshold.ui.alertType.descriptionText": "アグリゲーションされたクエリがしきい値に達したときにアラートを発行します。",
@ -44509,20 +44498,13 @@
"xpack.triggersActionsUI.connectors.home.description": "サードパーティソフトウェアをアラートデータに連携します。",
"xpack.triggersActionsUI.connectors.home.documentationButtonLabel": "ドキュメント",
"xpack.triggersActionsUI.connectors.home.logsTabTitle": "ログ",
"xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggType]が「{aggType}」のときには[aggField]に値が必要です",
"xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]が[dateEnd]よりも大です",
"xpack.triggersActionsUI.data.coreQueryParams.formattedFieldErrorMessage": "{fieldName}の無効な{formatName}形式:「{fieldValue}」",
"xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval][dateStart]が[dateEnd]と等しくない場合に指定する必要があります",
"xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage": "無効な aggType「{aggType}」",
"xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage": "無効な日付{date}",
"xpack.triggersActionsUI.data.coreQueryParams.invalidDurationErrorMessage": "無効な期間:「{duration}」",
"xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage": "無効なgroupBy「{groupBy}」",
"xpack.triggersActionsUI.data.coreQueryParams.invalidKQLQueryErrorMessage": "フィルタークエリは無効です。",
"xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage": "[termSize]{maxGroups}以下でなければなりません。",
"xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage": "無効な timeWindowUnit「{timeWindowUnit}」",
"xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage": "間隔{intervals}の計算値が{maxIntervals}よりも大です",
"xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage": "[termField][groupBy]がトップのときにはtermFieldが必要です",
"xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage": "[termSize][groupBy]がトップのときにはtermSizeが必要です",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "キャンセル",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "{numIdsToDelete, plural, one {{singleTitle}} other {# {multipleTitle}}}を削除",
"xpack.triggersActionsUI.globalAlerts.alertStats.disabled": "無効",

View file

@ -41046,16 +41046,8 @@
"xpack.stackAlerts.esQuery.alertTypeContextReasonDescription": "过去 {window}{verb}{index} 的文档计数为 {value}。{comparator} {threshold} 时告警。",
"xpack.stackAlerts.esQuery.alertTypeTitle": "Elasticsearch 查询",
"xpack.stackAlerts.esQuery.esqlAlertTypeContextConditionsDescription": "查询{negation} 文档{groupCondition}",
"xpack.stackAlerts.esQuery.esqlThresholdComparatorErrorMessage": "[thresholdComparator]:必须大于",
"xpack.stackAlerts.esQuery.esqlThresholdErrorMessage": "[threshold]:必须为 0",
"xpack.stackAlerts.esQuery.esqlTimeFieldErrorMessage": "[timeField]:必填",
"xpack.stackAlerts.esQuery.invalidComparatorErrorMessage": "指定的 thresholdComparator 无效:{comparator}",
"xpack.stackAlerts.esQuery.invalidEsQueryErrorMessage": "[esQuery]:必须是有效的 JSON",
"xpack.stackAlerts.esQuery.invalidTermSizeMaximumErrorMessage": "[termSize]:必须小于或等于 {maxGroups}",
"xpack.stackAlerts.esQuery.missingEsQueryErrorMessage": "[esQuery]:必须包含'query'",
"xpack.stackAlerts.esQuery.serverless.sizeErrorMessage": "[size]:必须小于或等于 {maxSize}",
"xpack.stackAlerts.esQuery.termFieldRequiredErrorMessage": "[termField][groupBy] 为 top 时termField 为必需",
"xpack.stackAlerts.esQuery.termSizeRequiredErrorMessage": "[termSize][groupBy] 为 top 时termSize 为必需",
"xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。",
"xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "Elasticsearch 查询规则 '{{rule.name}}' 处于活动状态:- 值:'{{context.value}}' - 满足的条件:'{{context.conditions}}' 超过 '{{rule.params.timeWindowSize}}''{{rule.params.timeWindowUnit}}' - 时间戳:'{{context.date}}' - 链接:'{{context.link}}'",
"xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "在运行最新查询期间找到匹配项时告警。",
@ -43858,10 +43850,7 @@
"xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval]:如果 [dateStart] 不等于 [dateEnd],则必须指定",
"xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage": "日期 {date} 无效",
"xpack.triggersActionsUI.data.coreQueryParams.invalidKQLQueryErrorMessage": "筛选查询无效。",
"xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage": "[termSize]:必须小于或等于 {maxGroups}",
"xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage": "时间间隔 {intervals} 的计算数目大于最大值 {maxIntervals}",
"xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage": "[termField][groupBy] 为 top 时termField 为必需",
"xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage": "[termSize][groupBy] 为 top 时termSize 为必需",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "取消",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "删除{numIdsToDelete, plural, one {{singleTitle}} other { # 个{multipleTitle}}}",
"xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.descriptionText": "无法恢复{numIdsToDelete, plural, one {删除的{singleTitle}} other {删除的{multipleTitle}}}。",

View file

@ -7,8 +7,6 @@
import { Comparator } from './comparator_types';
export type ComparatorFn = (value: number, threshold: number[]) => boolean;
const humanReadableComparators = new Map<Comparator, string>([
[Comparator.LT, 'less than'],
[Comparator.LT_OR_EQ, 'less than or equal to'],
@ -18,21 +16,6 @@ const humanReadableComparators = new Map<Comparator, string>([
[Comparator.NOT_BETWEEN, 'not between'],
]);
export const ComparatorFns = new Map<Comparator, ComparatorFn>([
[Comparator.LT, (value: number, threshold: number[]) => value < threshold[0]],
[Comparator.LT_OR_EQ, (value: number, threshold: number[]) => value <= threshold[0]],
[Comparator.GT_OR_EQ, (value: number, threshold: number[]) => value >= threshold[0]],
[Comparator.GT, (value: number, threshold: number[]) => value > threshold[0]],
[
Comparator.BETWEEN,
(value: number, threshold: number[]) => value >= threshold[0] && value <= threshold[1],
],
[
Comparator.NOT_BETWEEN,
(value: number, threshold: number[]) => value < threshold[0] || value > threshold[1],
],
]);
export const getComparatorScript = (
comparator: Comparator,
threshold: number[],
@ -72,8 +55,6 @@ export const getComparatorScript = (
}
};
export const ComparatorFnNames = new Set(ComparatorFns.keys());
export function getHumanReadableComparator(comparator: Comparator) {
return humanReadableComparators.has(comparator)
? humanReadableComparators.get(comparator)

View file

@ -5,12 +5,7 @@
* 2.0.
*/
export {
ComparatorFns,
getComparatorScript,
ComparatorFnNames,
getHumanReadableComparator,
} from './comparator';
export { getComparatorScript, getHumanReadableComparator } from './comparator';
export type { EsqlTable } from './esql_query_utils';
export { rowToDocument, transformDatatableToEsqlTable, toEsQueryHits } from './esql_query_utils';

View file

@ -10,7 +10,7 @@ import {
addMessages,
getContextConditionsDescription,
} from './action_context';
import { EsQueryRuleParams, EsQueryRuleParamsSchema } from './rule_type_params';
import { EsQueryRuleParams, EsQueryRuleParamsSchema } from '@kbn/response-ops-rule-params/es_query';
import { Comparator } from '../../../common/comparator_types';
describe('addMessages', () => {

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { AlertInstanceContext } from '@kbn/alerting-plugin/server';
import { EsQueryRuleParams } from './rule_type_params';
import type { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { Comparator } from '../../../common/comparator_types';
import { getHumanReadableComparator } from '../../../common';
import { isEsqlQueryRule } from './util';

View file

@ -14,7 +14,7 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
import { loggerMock } from '@kbn/logging-mocks';
import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
import { ISearchStartSearchSource } from '@kbn/data-plugin/common';
import { EsQueryRuleParams } from './rule_type_params';
import { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { FetchEsQueryOpts } from './lib/fetch_es_query';
import { FetchSearchSourceQueryOpts } from './lib/fetch_search_source_query';
import { FetchEsqlQueryOpts } from './lib/fetch_esql_query';

View file

@ -18,8 +18,9 @@ import {
} from '@kbn/rule-data-utils';
import { AlertsClientError } from '@kbn/alerting-plugin/server';
import { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { ComparatorFns } from '../../../common';
import { ComparatorFns } from '@kbn/response-ops-rule-params/common';
import {
addMessages,
EsQueryRuleActionContext,
@ -33,7 +34,6 @@ import {
} from './types';
import { ActionGroupId, ConditionMetAlertInstanceId } from './constants';
import { fetchEsQuery } from './lib/fetch_es_query';
import { EsQueryRuleParams } from './rule_type_params';
import { fetchSearchSourceQuery } from './lib/fetch_search_source_query';
import { isEsqlQueryRule, isSearchSourceRule } from './util';
import { fetchEsqlQuery } from './lib/fetch_esql_query';

View file

@ -12,7 +12,8 @@ import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/serve
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { getRuleType } from './rule_type';
import { EsQueryRuleParams, EsQueryRuleState } from './rule_type_params';
import { EsQueryRuleState } from './rule_type_params';
import { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { ActionContext } from './action_context';
import type { ESSearchResponse, ESSearchRequest } from '@kbn/es-types';
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';

View file

@ -9,16 +9,19 @@ import { i18n } from '@kbn/i18n';
import { CoreSetup, DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { extractReferences, injectReferences } from '@kbn/data-plugin/common';
import { ES_QUERY_ID, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import {
EsQueryRuleParamsSchema,
type EsQueryRuleParams,
} from '@kbn/response-ops-rule-params/es_query';
import { STACK_ALERTS_AAD_CONFIG } from '..';
import { RuleType } from '../../types';
import { ActionContext } from './action_context';
import {
EsQueryRuleParams,
EsQueryRuleParamsExtractedParams,
EsQueryRuleParamsSchema,
EsQueryRuleState,
validateServerless,
} from './rule_type_params';
import { ExecutorOptions } from './types';
import { ActionGroupId } from './constants';
import { executor } from './executor';

View file

@ -6,11 +6,12 @@
*/
import { TypeOf } from '@kbn/config-schema';
import { MAX_GROUPS } from '@kbn/triggers-actions-ui-plugin/server';
import { MAX_GROUPS } from '@kbn/response-ops-rule-params/common';
import type { Writable } from '@kbn/utility-types';
import { Comparator } from '../../../common/comparator_types';
import { ES_QUERY_MAX_HITS_PER_EXECUTION } from '../../../common';
import { EsQueryRuleParamsSchema, EsQueryRuleParams, validateServerless } from './rule_type_params';
import { validateServerless } from './rule_type_params';
import { EsQueryRuleParamsSchema, EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
const DefaultParams: Writable<Partial<EsQueryRuleParams>> = {
index: ['index-name'],

View file

@ -6,30 +6,11 @@
*/
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import {
validateTimeWindowUnits,
validateAggType,
validateGroupBy,
MAX_GROUPS,
} from '@kbn/triggers-actions-ui-plugin/server';
import { RuleTypeState } from '@kbn/alerting-plugin/server';
import { type EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import {
MAX_SELECTABLE_SOURCE_FIELDS,
MAX_SELECTABLE_GROUP_BY_TERMS,
} from '../../../common/constants';
import {
ComparatorFnNames,
ES_QUERY_MAX_HITS_PER_EXECUTION,
ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS,
} from '../../../common';
import { Comparator } from '../../../common/comparator_types';
import { getComparatorSchemaType } from '../lib/comparator';
import { isEsqlQueryRule, isSearchSourceRule } from './util';
import { ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS } from '../../../common';
// rule type parameters
export type EsQueryRuleParams = TypeOf<typeof EsQueryRuleParamsSchema>;
export interface EsQueryRuleState extends RuleTypeState {
latestTimestamp: string | undefined;
}
@ -40,181 +21,6 @@ export type EsQueryRuleParamsExtractedParams = Omit<EsQueryRuleParams, 'searchCo
};
};
const EsQueryRuleParamsSchemaProperties = {
size: schema.number({ min: 0, max: ES_QUERY_MAX_HITS_PER_EXECUTION }),
timeWindowSize: schema.number({ min: 1 }),
excludeHitsFromPreviousRun: schema.boolean({ defaultValue: true }),
timeWindowUnit: schema.string({ validate: validateTimeWindowUnits }),
threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }),
thresholdComparator: getComparatorSchemaType(validateComparator),
// aggregation type
aggType: schema.string({ validate: validateAggType, defaultValue: 'count' }),
// aggregation field
aggField: schema.maybe(schema.string({ minLength: 1 })),
// how to group
groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }),
// field to group on (for groupBy: top)
termField: schema.maybe(
schema.oneOf([
schema.string({ minLength: 1 }),
schema.arrayOf(schema.string(), { minSize: 2, maxSize: MAX_SELECTABLE_GROUP_BY_TERMS }),
])
),
// limit on number of groups returned
termSize: schema.maybe(schema.number({ min: 1 })),
searchType: schema.oneOf(
[schema.literal('searchSource'), schema.literal('esQuery'), schema.literal('esqlQuery')],
{
defaultValue: 'esQuery',
}
),
timeField: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.string({ minLength: 1 }),
schema.maybe(schema.string({ minLength: 1 }))
),
// searchSource rule param only
searchConfiguration: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('searchSource'),
schema.object({}, { unknowns: 'allow' }),
schema.never()
),
// esQuery rule params only
esQuery: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.string({ minLength: 1 }),
schema.never()
),
index: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esQuery'),
schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }),
schema.never()
),
// esqlQuery rule params only
esqlQuery: schema.conditional(
schema.siblingRef('searchType'),
schema.literal('esqlQuery'),
schema.object({ esql: schema.string({ minLength: 1 }) }),
schema.never()
),
sourceFields: schema.maybe(
schema.arrayOf(
schema.object({
label: schema.string(),
searchPath: schema.string(),
}),
{
maxSize: MAX_SELECTABLE_SOURCE_FIELDS,
}
)
),
};
export const EsQueryRuleParamsSchema = schema.object(EsQueryRuleParamsSchemaProperties, {
validate: validateParams,
});
const betweenComparators = new Set(['between', 'notBetween']);
// using direct type not allowed, circular reference, so body is typed to any
function validateParams(anyParams: unknown): string | undefined {
const {
esQuery,
thresholdComparator,
threshold,
searchType,
aggType,
aggField,
groupBy,
termField,
termSize,
} = anyParams as EsQueryRuleParams;
if (betweenComparators.has(thresholdComparator) && threshold.length === 1) {
return i18n.translate('xpack.stackAlerts.esQuery.invalidThreshold2ErrorMessage', {
defaultMessage:
'[threshold]: must have two elements for the "{thresholdComparator}" comparator',
values: {
thresholdComparator,
},
});
}
if (aggType !== 'count' && !aggField) {
return i18n.translate('xpack.stackAlerts.esQuery.aggTypeRequiredErrorMessage', {
defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"',
values: {
aggType,
},
});
}
// check grouping
if (groupBy === 'top') {
if (termField == null) {
return i18n.translate('xpack.stackAlerts.esQuery.termFieldRequiredErrorMessage', {
defaultMessage: '[termField]: termField required when [groupBy] is top',
});
}
if (termSize == null) {
return i18n.translate('xpack.stackAlerts.esQuery.termSizeRequiredErrorMessage', {
defaultMessage: '[termSize]: termSize required when [groupBy] is top',
});
}
if (termSize > MAX_GROUPS) {
return i18n.translate('xpack.stackAlerts.esQuery.invalidTermSizeMaximumErrorMessage', {
defaultMessage: '[termSize]: must be less than or equal to {maxGroups}',
values: {
maxGroups: MAX_GROUPS,
},
});
}
}
if (isSearchSourceRule(searchType)) {
return;
}
if (isEsqlQueryRule(searchType)) {
const { timeField } = anyParams as EsQueryRuleParams;
if (!timeField) {
return i18n.translate('xpack.stackAlerts.esQuery.esqlTimeFieldErrorMessage', {
defaultMessage: '[timeField]: is required',
});
}
if (thresholdComparator !== Comparator.GT) {
return i18n.translate('xpack.stackAlerts.esQuery.esqlThresholdComparatorErrorMessage', {
defaultMessage: '[thresholdComparator]: is required to be greater than',
});
}
if (threshold && threshold[0] !== 0) {
return i18n.translate('xpack.stackAlerts.esQuery.esqlThresholdErrorMessage', {
defaultMessage: '[threshold]: is required to be 0',
});
}
return;
}
try {
const parsedQuery = JSON.parse(esQuery);
if (parsedQuery && !parsedQuery.query) {
return i18n.translate('xpack.stackAlerts.esQuery.missingEsQueryErrorMessage', {
defaultMessage: '[esQuery]: must contain "query"',
});
}
} catch (err) {
return i18n.translate('xpack.stackAlerts.esQuery.invalidEsQueryErrorMessage', {
defaultMessage: '[esQuery]: must be valid JSON',
});
}
}
export function validateServerless(params: EsQueryRuleParams) {
const { size } = params;
if (size > ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS) {
@ -228,14 +34,3 @@ export function validateServerless(params: EsQueryRuleParams) {
);
}
}
function validateComparator(comparator: Comparator): string | undefined {
if (ComparatorFnNames.has(comparator)) return;
return i18n.translate('xpack.stackAlerts.esQuery.invalidComparatorErrorMessage', {
defaultMessage: 'invalid thresholdComparator specified: {comparator}',
values: {
comparator,
},
});
}

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { RuleExecutorOptions, RuleTypeParams } from '../../types';
import { ActionContext } from './action_context';
import { EsQueryRuleParams, EsQueryRuleState } from './rule_type_params';
import { EsQueryRuleState } from './rule_type_params';
import { ActionGroupId } from './constants';
import { StackAlertType } from '../types';

View file

@ -7,8 +7,8 @@
import { i18n } from '@kbn/i18n';
import { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { EsQueryRuleParams } from '@kbn/response-ops-rule-params/es_query';
import { OnlyEsQueryRuleParams } from './types';
import { EsQueryRuleParams } from './rule_type_params';
export function isEsQueryRule(searchType: EsQueryRuleParams['searchType']) {
return searchType === 'esQuery';

View file

@ -6,7 +6,7 @@
*/
import { BaseActionContext, addMessages } from './action_context';
import { ParamsSchema } from './rule_type_params';
import { ParamsSchema } from '@kbn/response-ops-rule-params/index_threshold';
describe('ActionContext', () => {
it('generates expected properties if aggField is null', async () => {

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { AlertInstanceContext } from '@kbn/alerting-plugin/server';
import { Params } from './rule_type_params';
import type { Params } from '@kbn/response-ops-rule-params/index_threshold';
// rule type context provided to actions
export interface ActionContext extends BaseActionContext {

View file

@ -10,7 +10,6 @@ import { getRuleType } from './rule_type';
// future enhancement: make these configurable?
export const MAX_INTERVALS = 1000;
export const MAX_GROUPS = 1000;
export const DEFAULT_GROUPS = 100;
export function register(params: RegisterRuleTypesParams) {

View file

@ -12,7 +12,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks';
import { RuleExecutorServices } from '@kbn/alerting-plugin/server';
import { getRuleType, ActionGroupId } from './rule_type';
import { ActionContext } from './action_context';
import { Params } from './rule_type_params';
import type { Params } from '@kbn/response-ops-rule-params/index_threshold';
import { TIME_SERIES_BUCKET_SELECTOR_FIELD } from '@kbn/triggers-actions-ui-plugin/server';
import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/server/mocks';
import { Comparator } from '../../../common/comparator_types';

View file

@ -18,10 +18,11 @@ import {
STACK_ALERTS_FEATURE_ID,
} from '@kbn/rule-data-utils';
import { AlertsClientError } from '@kbn/alerting-plugin/server';
import { type Params, ParamsSchema } from '@kbn/response-ops-rule-params/index_threshold';
import { ComparatorFns } from '@kbn/response-ops-rule-params/common';
import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE, STACK_ALERTS_AAD_CONFIG } from '..';
import { ComparatorFns, getComparatorScript, getHumanReadableComparator } from '../../../common';
import { getComparatorScript, getHumanReadableComparator } from '../../../common';
import { ActionContext, BaseActionContext, addMessages } from './action_context';
import { Params, ParamsSchema } from './rule_type_params';
import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types';
import { StackAlertType } from '../types';

View file

@ -5,10 +5,14 @@
* 2.0.
*/
import { ParamsSchema, Params } from './rule_type_params';
import {
ParamsSchema,
type Params,
type CoreQueryParams,
} from '@kbn/response-ops-rule-params/index_threshold';
import { MAX_GROUPS } from '@kbn/response-ops-rule-params/common';
import { ObjectType, TypeOf } from '@kbn/config-schema';
import type { Writable } from '@kbn/utility-types';
import { CoreQueryParams, MAX_GROUPS } from '@kbn/triggers-actions-ui-plugin/server';
import { Comparator } from '../../../common/comparator_types';
const DefaultParams: Writable<Partial<Params>> = {

View file

@ -1,64 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import {
CoreQueryParamsSchemaProperties,
validateCoreQueryBody,
} from '@kbn/triggers-actions-ui-plugin/server';
import { ComparatorFnNames } from '../../../common';
import { Comparator } from '../../../common/comparator_types';
import { getComparatorSchemaType } from '../lib/comparator';
// rule type parameters
export type Params = TypeOf<typeof ParamsSchema>;
export const ParamsSchema = schema.object(
{
...CoreQueryParamsSchemaProperties,
// the comparison function to use to determine if the threshold as been met
thresholdComparator: getComparatorSchemaType(validateComparator),
// the values to use as the threshold; `between` and `notBetween` require
// two values, the others require one.
threshold: schema.arrayOf(schema.number(), { minSize: 1, maxSize: 2 }),
},
{ validate: validateParams }
);
const betweenComparators = new Set(['between', 'notBetween']);
// using direct type not allowed, circular reference, so body is typed to any
function validateParams(anyParams: unknown): string | undefined {
// validate core query parts, return if it fails validation (returning string)
const coreQueryValidated = validateCoreQueryBody(anyParams);
if (coreQueryValidated) return coreQueryValidated;
const { thresholdComparator, threshold }: Params = anyParams as Params;
if (betweenComparators.has(thresholdComparator) && threshold.length === 1) {
return i18n.translate('xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage', {
defaultMessage:
'[threshold]: must have two elements for the "{thresholdComparator}" comparator',
values: {
thresholdComparator,
},
});
}
}
function validateComparator(comparator: Comparator): string | undefined {
if (ComparatorFnNames.has(comparator)) return;
return i18n.translate('xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage', {
defaultMessage: 'invalid thresholdComparator specified: {comparator}',
values: {
comparator,
},
});
}

View file

@ -1,21 +0,0 @@
/*
* 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 { Comparator } from '../../../common/comparator_types';
export const getComparatorSchemaType = (validate: (comparator: Comparator) => string | void) =>
schema.oneOf(
[
schema.literal(Comparator.GT),
schema.literal(Comparator.LT),
schema.literal(Comparator.GT_OR_EQ),
schema.literal(Comparator.LT_OR_EQ),
schema.literal(Comparator.BETWEEN),
schema.literal(Comparator.NOT_BETWEEN),
],
{ validate }
);

View file

@ -1,14 +1,9 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"outDir": "target/types"
},
"include": [
"server/**/*",
"server/**/*.json",
"public/**/*",
"common/*"
],
"include": ["server/**/*", "server/**/*.json", "public/**/*", "common/*"],
"kbn_references": [
"@kbn/core",
"@kbn/alerting-plugin",
@ -58,7 +53,5 @@
"@kbn/alerting-rule-utils",
"@kbn/response-ops-rule-params"
],
"exclude": [
"target/**/*",
]
"exclude": ["target/**/*"]
}

View file

@ -9,18 +9,10 @@ import { Logger, IRouter } from '@kbn/core/server';
import { timeSeriesQuery } from './lib/time_series_query';
import { registerRoutes } from './routes';
export type { TimeSeriesQuery, CoreQueryParams } from './lib';
export {
TIME_SERIES_BUCKET_SELECTOR_FIELD,
CoreQueryParamsSchemaProperties,
validateCoreQueryBody,
validateTimeWindowUnits,
validateAggType,
validateGroupBy,
} from './lib';
export type { TimeSeriesQuery } from './lib';
export { TIME_SERIES_BUCKET_SELECTOR_FIELD } from './lib';
// future enhancement: make these configurable?
export const MAX_GROUPS = 1000;
export const DEFAULT_GROUPS = 100;
export function getService() {

View file

@ -9,8 +9,8 @@
import { ObjectType } from '@kbn/config-schema';
import type { Writable } from '@kbn/utility-types';
import { CoreQueryParams } from './core_query_types';
import { MAX_GROUPS } from '..';
import type { CoreQueryParams } from '@kbn/response-ops-rule-params/index_threshold';
import { MAX_GROUPS } from '@kbn/response-ops-rule-params/common';
const DefaultParams: Writable<Partial<CoreQueryParams>> = {
index: 'index-name',

View file

@ -8,136 +8,8 @@
// common properties on time_series_query and alert_type_params
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query';
import { MAX_GROUPS } from '..';
export const CoreQueryParamsSchemaProperties = {
// name of the indices to search
index: schema.oneOf([
schema.string({ minLength: 1 }),
schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }),
]),
// field in index used for date/time
timeField: schema.string({ minLength: 1 }),
// aggregation type
aggType: schema.string({ validate: validateAggType, defaultValue: 'count' }),
// aggregation field
aggField: schema.maybe(schema.string({ minLength: 1 })),
// how to group
groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }),
// field to group on (for groupBy: top)
termField: schema.maybe(schema.string({ minLength: 1 })),
// filter field
filterKuery: schema.maybe(schema.string({ validate: validateKuery })),
// limit on number of groups returned
termSize: schema.maybe(schema.number({ min: 1 })),
// size of time window for date range aggregations
timeWindowSize: schema.number({ min: 1 }),
// units of time window for date range aggregations
timeWindowUnit: schema.string({ validate: validateTimeWindowUnits }),
};
const CoreQueryParamsSchema = schema.object(CoreQueryParamsSchemaProperties);
export type CoreQueryParams = TypeOf<typeof CoreQueryParamsSchema>;
// Meant to be used in a "subclass"'s schema body validator, so the
// anyParams object is assumed to have been validated with the schema
// above.
// Using direct type not allowed, circular reference, so body is typed to unknown.
export function validateCoreQueryBody(anyParams: unknown): string | undefined {
const { aggType, aggField, groupBy, termField, termSize }: CoreQueryParams =
anyParams as CoreQueryParams;
if (aggType !== 'count' && !aggField) {
return i18n.translate(
'xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage',
{
defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"',
values: {
aggType,
},
}
);
}
// check grouping
if (groupBy === 'top') {
if (termField == null) {
return i18n.translate(
'xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage',
{
defaultMessage: '[termField]: termField required when [groupBy] is top',
}
);
}
if (termSize == null) {
return i18n.translate(
'xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage',
{
defaultMessage: '[termSize]: termSize required when [groupBy] is top',
}
);
}
if (termSize > MAX_GROUPS) {
return i18n.translate(
'xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage',
{
defaultMessage: '[termSize]: must be less than or equal to {maxGroups}',
values: {
maxGroups: MAX_GROUPS,
},
}
);
}
}
}
const AggTypes = new Set(['count', 'avg', 'min', 'max', 'sum']);
export function validateAggType(aggType: string): string | undefined {
if (AggTypes.has(aggType)) {
return;
}
return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage', {
defaultMessage: 'invalid aggType: "{aggType}"',
values: {
aggType,
},
});
}
export function validateGroupBy(groupBy: string): string | undefined {
if (groupBy === 'all' || groupBy === 'top') {
return;
}
return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage', {
defaultMessage: 'invalid groupBy: "{groupBy}"',
values: {
groupBy,
},
});
}
const TimeWindowUnits = new Set(['s', 'm', 'h', 'd']);
export function validateTimeWindowUnits(timeWindowUnit: string): string | undefined {
if (TimeWindowUnits.has(timeWindowUnit)) {
return;
}
return i18n.translate(
'xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage',
{
defaultMessage: 'invalid timeWindowUnit: "{timeWindowUnit}"',
values: {
timeWindowUnit,
},
}
);
}
export function validateKuery(query: string): string | undefined {
try {

View file

@ -7,11 +7,3 @@
export type { TimeSeriesQuery } from './time_series_query';
export { TIME_SERIES_BUCKET_SELECTOR_FIELD } from './time_series_query';
export type { CoreQueryParams } from './core_query_types';
export {
CoreQueryParamsSchemaProperties,
validateCoreQueryBody,
validateTimeWindowUnits,
validateAggType,
validateGroupBy,
} from './core_query_types';

View file

@ -12,7 +12,10 @@ import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { parseDuration } from '@kbn/alerting-plugin/server';
import { CoreQueryParamsSchemaProperties, validateCoreQueryBody } from './core_query_types';
import {
CoreQueryParamsSchemaProperties,
validateCoreQueryBody,
} from '@kbn/response-ops-rule-params/index_threshold';
import {
MAX_INTERVALS,
getDateStartAfterDateEndErrorMessage,

View file

@ -8,17 +8,8 @@ import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/serv
import { configSchema, ConfigSchema } from './config';
export type { PluginStartContract } from './plugin';
export type { TimeSeriesQuery, CoreQueryParams } from './data';
export {
CoreQueryParamsSchemaProperties,
validateCoreQueryBody,
validateTimeWindowUnits,
validateAggType,
validateGroupBy,
MAX_GROUPS,
DEFAULT_GROUPS,
TIME_SERIES_BUCKET_SELECTOR_FIELD,
} from './data';
export type { TimeSeriesQuery } from './data';
export { DEFAULT_GROUPS, TIME_SERIES_BUCKET_SELECTOR_FIELD } from './data';
export const config: PluginConfigDescriptor<ConfigSchema> = {
exposeToBrowser: {

View file

@ -75,7 +75,8 @@
"@kbn/response-ops-rule-form",
"@kbn/charts-theme",
"@kbn/rrule",
"@kbn/core-notifications-browser-mocks"
"@kbn/core-notifications-browser-mocks",
"@kbn/response-ops-rule-params"
],
"exclude": ["target/**/*"]
}