mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ResponseOps] Schema changes for ES|QL rule type improvements - adding grouping per row (#217898)
Related to https://github.com/elastic/response-ops-team/issues/201 ## Summary Schema changes for intermediate release related to this PR, https://github.com/elastic/kibana/pull/212135. This PR adds a new `row` option and validation for the ES query rule `groupBy` field. ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
5454ce5bbd
commit
5667c6cc43
4 changed files with 202 additions and 28 deletions
|
@ -125,7 +125,7 @@ export function validateAggType(aggType: string): string | undefined {
|
|||
}
|
||||
|
||||
export function validateGroupBy(groupBy: string): string | undefined {
|
||||
if (groupBy === 'all' || groupBy === 'top') {
|
||||
if (groupBy === 'all' || groupBy === 'top' || groupBy === 'row') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 { validateParams } from './v1';
|
||||
|
||||
describe('validateParams', () => {
|
||||
describe('esqlQuery', () => {
|
||||
const params = {
|
||||
searchType: 'esqlQuery',
|
||||
threshold: [0],
|
||||
thresholdComparator: '>',
|
||||
timeField: '@timestamp',
|
||||
};
|
||||
|
||||
it('if timeField is not defined should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
timeField: undefined,
|
||||
})
|
||||
).toBe('[timeField]: is required');
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
timeField: '',
|
||||
})
|
||||
).toBe('[timeField]: is required');
|
||||
});
|
||||
|
||||
it('if thresholdComparator is not > should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
thresholdComparator: '<',
|
||||
})
|
||||
).toBe('[thresholdComparator]: is required to be greater than');
|
||||
});
|
||||
|
||||
it('if threshold is not 0 should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
threshold: [8],
|
||||
})
|
||||
).toBe('[threshold]: is required to be 0');
|
||||
});
|
||||
|
||||
it('if groupBy is "top" should not return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
groupBy: 'top',
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
const esQuery = [
|
||||
'esQuery',
|
||||
{
|
||||
aggType: 'count',
|
||||
esQuery: '{"query":{"match_all":{}}}',
|
||||
groupBy: 'all',
|
||||
searchType: 'esQuery',
|
||||
threshold: [0],
|
||||
thresholdComparator: '>',
|
||||
},
|
||||
] as const;
|
||||
const searchSource = [
|
||||
'searchSource',
|
||||
{
|
||||
aggType: 'count',
|
||||
groupBy: 'all',
|
||||
searchType: 'searchSource',
|
||||
threshold: [0],
|
||||
thresholdComparator: '>',
|
||||
},
|
||||
] as const;
|
||||
for (const [searchType, params] of [esQuery, searchSource]) {
|
||||
describe(searchType, () => {
|
||||
it('if thresholdComparator is a "betweenComparator" and threshold does not have two elements should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
thresholdComparator: 'between',
|
||||
})
|
||||
).toBe('[threshold]: must have two elements for the "between" comparator');
|
||||
});
|
||||
|
||||
it('if aggType is not "count" and aggField is not defined should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
aggType: 'avg',
|
||||
aggField: undefined,
|
||||
})
|
||||
).toBe('[aggField]: must have a value when [aggType] is "avg"');
|
||||
});
|
||||
|
||||
it('if groupBy is "top" and termField is undefined should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
groupBy: 'top',
|
||||
termField: undefined,
|
||||
})
|
||||
).toBe('[termField]: termField required when [groupBy] is top');
|
||||
});
|
||||
|
||||
it('if groupBy is "top" and termSize is undefined should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
groupBy: 'top',
|
||||
termField: 'test',
|
||||
termSize: undefined,
|
||||
})
|
||||
).toBe('[termSize]: termSize required when [groupBy] is top');
|
||||
});
|
||||
|
||||
it('if groupBy is "top" and termSize is > MAX_GROUPS should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
groupBy: 'top',
|
||||
termField: 'test',
|
||||
termSize: 1001,
|
||||
})
|
||||
).toBe('[termSize]: must be less than or equal to 1000');
|
||||
});
|
||||
|
||||
it('if groupBy is "row" should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
groupBy: 'row',
|
||||
})
|
||||
).toBe('[groupBy]: groupBy should be all or top when [searchType] is not esqlQuery');
|
||||
});
|
||||
|
||||
if (searchType === 'esQuery') {
|
||||
it('if parsed esQuery does not contain query should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
esQuery: '{}',
|
||||
})
|
||||
).toBe('[esQuery]: must contain "query"');
|
||||
});
|
||||
|
||||
it('if esQuery is not valid JSON should return error message', () => {
|
||||
expect(
|
||||
validateParams({
|
||||
...params,
|
||||
esQuery: '{"query":{"match_all":{}}',
|
||||
})
|
||||
).toBe('[esQuery]: must be valid JSON');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -86,7 +86,7 @@ const EsQueryRuleParamsSchemaProperties = {
|
|||
defaultValue: 'all',
|
||||
meta: {
|
||||
description:
|
||||
'Indicates whether the aggregation is applied over all documents (`all`) or split into groups (`top`) using a grouping field (`termField`). If grouping is used, an alert will be created for each group when it exceeds the threshold; only the top groups (up to `termSize` number of groups) are checked.',
|
||||
'Indicates whether the aggregation is applied over all documents (`all`), grouped by row (`row`), or split into groups (`top`) using a grouping field (`termField`) where only the top groups (up to `termSize` number of groups) are checked. If grouping is used, an alert will be created for each group when it exceeds the threshold.',
|
||||
},
|
||||
}),
|
||||
termField: schema.maybe(
|
||||
|
@ -187,7 +187,7 @@ function isEsqlQueryRule(searchType: EsQueryRuleParams['searchType']) {
|
|||
}
|
||||
|
||||
// using direct type not allowed, circular reference, so body is typed to any
|
||||
function validateParams(anyParams: unknown): string | undefined {
|
||||
export function validateParams(anyParams: unknown): string | undefined {
|
||||
const {
|
||||
esQuery,
|
||||
thresholdComparator,
|
||||
|
@ -200,6 +200,31 @@ function validateParams(anyParams: unknown): string | undefined {
|
|||
termSize,
|
||||
} = anyParams as EsQueryRuleParams;
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
// The esqlQuery type does not validate groupBy, as any groupBy other than 'row' is considered to be 'all'
|
||||
return;
|
||||
}
|
||||
|
||||
if (betweenComparators.has(thresholdComparator) && threshold.length === 1) {
|
||||
return i18n.translate('responseOps.ruleParams.esQuery.invalidThreshold2ErrorMessage', {
|
||||
defaultMessage:
|
||||
|
@ -243,32 +268,13 @@ function validateParams(anyParams: unknown): string | undefined {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSearchSourceRule(searchType)) {
|
||||
return;
|
||||
if (groupBy === 'row') {
|
||||
return i18n.translate('xpack.responseOps.ruleParams.esQuery.invalidRowGroupByErrorMessage', {
|
||||
defaultMessage: '[groupBy]: groupBy should be all or top when [searchType] is not esqlQuery',
|
||||
});
|
||||
}
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
if (isSearchSourceRule(searchType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ Object {
|
|||
"groupBy": Object {
|
||||
"flags": Object {
|
||||
"default": "all",
|
||||
"description": "Indicates whether the aggregation is applied over all documents (\`all\`) or split into groups (\`top\`) using a grouping field (\`termField\`). If grouping is used, an alert will be created for each group when it exceeds the threshold; only the top groups (up to \`termSize\` number of groups) are checked.",
|
||||
"description": "Indicates whether the aggregation is applied over all documents (\`all\`), grouped by row (\`row\`), or split into groups (\`top\`) using a grouping field (\`termField\`) where only the top groups (up to \`termSize\` number of groups) are checked. If grouping is used, an alert will be created for each group when it exceeds the threshold.",
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue