mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Serverless] Add lower size limit to Serverless elasticsearch query rule (#180520)
Resolves https://github.com/elastic/response-ops-team/issues/154 ## Summary This PR updates the max size to 100 for the ES Query rule and sets the default size to 10. ### 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 ### To verify 1. Run serverless ``` yarn es serverless --projectType oblt yarn serverless-oblt ``` 3. Create an ES Query rule in the UI and verify that the default value for `size` is 10, and you can't save a rule with `size` > 100 4. Go to [dev tools](http://localhost:5601/app/dev_tools#/console) and create a rule, verify that you get an error if the `size` > 100 ``` POST kbn:/api/alerting/rule { "params": { "searchType": "esQuery", "timeWindowSize": 5, "timeWindowUnit": "m", "threshold": [ 1000 ], "thresholdComparator": ">", "size": 1000, "esQuery": "{\n \"query\":{\n \"match_all\" : {}\n }\n }", "aggType": "count", "groupBy": "all", "termSize": 5, "excludeHitsFromPreviousRun": false, "sourceFields": [], "index": [ ".kibana" ], "timeField": "created_at" }, "consumer": "stackAlerts", "schedule": { "interval": "1m" }, "tags": [ "serverless" ], "name": "test", "rule_type_id": ".es-query", "actions": [] } ```
This commit is contained in:
parent
e7e5105965
commit
f447ed09c5
19 changed files with 180 additions and 48 deletions
|
@ -21,3 +21,6 @@ export const validSourceFields = [
|
|||
CONTAINER_ID,
|
||||
KUBERNETES_POD_UID,
|
||||
];
|
||||
|
||||
export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000;
|
||||
export const ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS = 100;
|
||||
|
|
|
@ -14,3 +14,8 @@ export {
|
|||
|
||||
export type { EsqlTable } from './esql_query_utils';
|
||||
export { rowToDocument, transformDatatableToEsqlTable, toEsQueryHits } from './esql_query_utils';
|
||||
|
||||
export {
|
||||
ES_QUERY_MAX_HITS_PER_EXECUTION,
|
||||
ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS,
|
||||
} from './constants';
|
||||
|
|
|
@ -25,6 +25,9 @@ export const DEFAULT_VALUES = {
|
|||
CAN_SELECT_MULTI_TERMS: true,
|
||||
SOURCE_FIELDS: [],
|
||||
};
|
||||
export const SERVERLESS_DEFAULT_VALUES = {
|
||||
SIZE: 10,
|
||||
};
|
||||
|
||||
export const COMMON_EXPRESSION_ERRORS = {
|
||||
searchType: new Array<string>(),
|
||||
|
|
|
@ -31,7 +31,7 @@ import { hasExpressionValidationErrors } from '../validation';
|
|||
import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query';
|
||||
import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types';
|
||||
import { IndexSelectPopover } from '../../components/index_select_popover';
|
||||
import { DEFAULT_VALUES } from '../constants';
|
||||
import { DEFAULT_VALUES, SERVERLESS_DEFAULT_VALUES } from '../constants';
|
||||
import { RuleCommonExpressions } from '../rule_common_expressions';
|
||||
import { convertRawRuntimeFieldtoFieldOption, useTriggerUiActionServices } from '../util';
|
||||
|
||||
|
@ -40,6 +40,9 @@ const { useXJsonMode } = XJson;
|
|||
export const EsQueryExpression: React.FC<
|
||||
RuleTypeParamsExpressionProps<EsQueryRuleParams<SearchType.esQuery>, EsQueryRuleMetaData>
|
||||
> = ({ ruleParams, setRuleParams, setRuleProperty, errors, data }) => {
|
||||
const services = useTriggerUiActionServices();
|
||||
const { http, docLinks, isServerless } = services;
|
||||
|
||||
const {
|
||||
index,
|
||||
timeField,
|
||||
|
@ -65,7 +68,7 @@ export const EsQueryExpression: React.FC<
|
|||
timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT,
|
||||
threshold: threshold ?? DEFAULT_VALUES.THRESHOLD,
|
||||
thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR,
|
||||
size: size ?? DEFAULT_VALUES.SIZE,
|
||||
size: size ? size : isServerless ? SERVERLESS_DEFAULT_VALUES.SIZE : DEFAULT_VALUES.SIZE,
|
||||
esQuery: esQuery ?? DEFAULT_VALUES.QUERY,
|
||||
aggType: aggType ?? DEFAULT_VALUES.AGGREGATION_TYPE,
|
||||
groupBy: groupBy ?? DEFAULT_VALUES.GROUP_BY,
|
||||
|
@ -88,9 +91,6 @@ export const EsQueryExpression: React.FC<
|
|||
[setRuleParams]
|
||||
);
|
||||
|
||||
const services = useTriggerUiActionServices();
|
||||
const { http, docLinks } = services;
|
||||
|
||||
const [esFields, setEsFields] = useState<FieldOption[]>([]);
|
||||
const [runtimeFields, setRuntimeFields] = useState<FieldOption[]>([]);
|
||||
const [combinedFields, setCombinedFields] = useState<FieldOption[]>([]);
|
||||
|
@ -134,7 +134,7 @@ export const EsQueryExpression: React.FC<
|
|||
const isGroupAgg = isGroupAggregation(termField);
|
||||
const isCountAgg = isCountAggregation(aggType);
|
||||
const window = `${timeWindowSize}${timeWindowUnit}`;
|
||||
if (hasExpressionValidationErrors(currentRuleParams)) {
|
||||
if (hasExpressionValidationErrors(currentRuleParams, isServerless)) {
|
||||
return {
|
||||
testResults: { results: [], truncated: false },
|
||||
isGrouped: isGroupAgg,
|
||||
|
@ -191,6 +191,7 @@ export const EsQueryExpression: React.FC<
|
|||
termSize,
|
||||
threshold,
|
||||
thresholdComparator,
|
||||
isServerless,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
@ -342,7 +343,7 @@ export const EsQueryExpression: React.FC<
|
|||
[setParam]
|
||||
)}
|
||||
errors={errors}
|
||||
hasValidationErrors={hasExpressionValidationErrors(currentRuleParams)}
|
||||
hasValidationErrors={hasExpressionValidationErrors(currentRuleParams, isServerless)}
|
||||
onTestFetch={onTestQuery}
|
||||
excludeHitsFromPreviousRun={excludeHitsFromPreviousRun}
|
||||
onChangeExcludeHitsFromPreviousRun={useCallback(
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { SourceFields } from '../../components/source_fields_select';
|
||||
import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types';
|
||||
import { DEFAULT_VALUES } from '../constants';
|
||||
import { DEFAULT_VALUES, SERVERLESS_DEFAULT_VALUES } from '../constants';
|
||||
import { useTriggerUiActionServices } from '../util';
|
||||
import { hasExpressionValidationErrors } from '../validation';
|
||||
import { TestQueryRow } from '../test_query_row';
|
||||
|
@ -41,7 +41,7 @@ import { rowToDocument, toEsQueryHits, transformDatatableToEsqlTable } from '../
|
|||
export const EsqlQueryExpression: React.FC<
|
||||
RuleTypeParamsExpressionProps<EsQueryRuleParams<SearchType.esqlQuery>, EsQueryRuleMetaData>
|
||||
> = ({ ruleParams, setRuleParams, setRuleProperty, errors }) => {
|
||||
const { expressions, http, fieldFormats } = useTriggerUiActionServices();
|
||||
const { expressions, http, fieldFormats, isServerless } = useTriggerUiActionServices();
|
||||
const { esqlQuery, timeWindowSize, timeWindowUnit, timeField, sourceFields } = ruleParams;
|
||||
|
||||
const [currentRuleParams, setCurrentRuleParams] = useState<
|
||||
|
@ -54,7 +54,7 @@ export const EsqlQueryExpression: React.FC<
|
|||
// so only 'met' results are returned, therefore the threshold should always be 0
|
||||
threshold: [0],
|
||||
thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR,
|
||||
size: DEFAULT_VALUES.SIZE,
|
||||
size: isServerless ? SERVERLESS_DEFAULT_VALUES.SIZE : DEFAULT_VALUES.SIZE,
|
||||
esqlQuery: esqlQuery ?? { esql: '' },
|
||||
aggType: DEFAULT_VALUES.AGGREGATION_TYPE,
|
||||
groupBy: DEFAULT_VALUES.GROUP_BY,
|
||||
|
@ -105,7 +105,7 @@ export const EsqlQueryExpression: React.FC<
|
|||
timeWindow: window,
|
||||
};
|
||||
|
||||
if (hasExpressionValidationErrors(currentRuleParams)) {
|
||||
if (hasExpressionValidationErrors(currentRuleParams, isServerless)) {
|
||||
return emptyResult;
|
||||
}
|
||||
const timeWindow = parseDuration(window);
|
||||
|
@ -160,6 +160,7 @@ export const EsqlQueryExpression: React.FC<
|
|||
expressions,
|
||||
fieldFormats,
|
||||
timeField,
|
||||
isServerless,
|
||||
]);
|
||||
|
||||
const refreshTimeFields = async (q: AggregateQuery) => {
|
||||
|
@ -309,7 +310,7 @@ export const EsqlQueryExpression: React.FC<
|
|||
<EuiSpacer />
|
||||
<TestQueryRow
|
||||
fetch={onTestQuery}
|
||||
hasValidationErrors={hasExpressionValidationErrors(currentRuleParams)}
|
||||
hasValidationErrors={hasExpressionValidationErrors(currentRuleParams, isServerless)}
|
||||
showTable
|
||||
/>
|
||||
</Fragment>
|
||||
|
|
|
@ -13,7 +13,7 @@ import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/p
|
|||
import { SavedQuery } from '@kbn/data-plugin/public';
|
||||
import { EsQueryRuleMetaData, EsQueryRuleParams, SearchType } from '../types';
|
||||
import { SearchSourceExpressionForm } from './search_source_expression_form';
|
||||
import { DEFAULT_VALUES } from '../constants';
|
||||
import { DEFAULT_VALUES, SERVERLESS_DEFAULT_VALUES } from '../constants';
|
||||
import { useTriggerUiActionServices } from '../util';
|
||||
|
||||
export type SearchSourceExpressionProps = RuleTypeParamsExpressionProps<
|
||||
|
@ -45,7 +45,7 @@ export const SearchSourceExpression = ({
|
|||
excludeHitsFromPreviousRun,
|
||||
sourceFields,
|
||||
} = ruleParams;
|
||||
const { data } = useTriggerUiActionServices();
|
||||
const { data, isServerless } = useTriggerUiActionServices();
|
||||
|
||||
const [searchSource, setSearchSource] = useState<ISearchSource>();
|
||||
const [savedQuery, setSavedQuery] = useState<SavedQuery>();
|
||||
|
@ -85,7 +85,7 @@ export const SearchSourceExpression = ({
|
|||
timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT,
|
||||
threshold: threshold ?? DEFAULT_VALUES.THRESHOLD,
|
||||
thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR,
|
||||
size: size ?? DEFAULT_VALUES.SIZE,
|
||||
size: size ? size : isServerless ? SERVERLESS_DEFAULT_VALUES.SIZE : DEFAULT_VALUES.SIZE,
|
||||
aggType: aggType ?? DEFAULT_VALUES.AGGREGATION_TYPE,
|
||||
aggField,
|
||||
groupBy: groupBy ?? DEFAULT_VALUES.GROUP_BY,
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
SearchType,
|
||||
SourceField,
|
||||
} from '../types';
|
||||
import { DEFAULT_VALUES } from '../constants';
|
||||
import { DEFAULT_VALUES, SERVERLESS_DEFAULT_VALUES } from '../constants';
|
||||
import { DataViewSelectPopover } from '../../components/data_view_select_popover';
|
||||
import { RuleCommonExpressions } from '../rule_common_expressions';
|
||||
import { useTriggerUiActionServices, convertFieldSpecToFieldOption } from '../util';
|
||||
|
@ -82,7 +82,7 @@ const isSearchSourceParam = (action: LocalStateAction): action is SearchSourcePa
|
|||
export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => {
|
||||
const services = useTriggerUiActionServices();
|
||||
const unifiedSearch = services.unifiedSearch;
|
||||
const { dataViews, dataViewEditor } = useTriggerUiActionServices();
|
||||
const { dataViews, dataViewEditor, isServerless } = useTriggerUiActionServices();
|
||||
const { searchSource, errors, initialSavedQuery, setParam, ruleParams } = props;
|
||||
const [savedQuery, setSavedQuery] = useState<SavedQuery>();
|
||||
|
||||
|
@ -115,7 +115,11 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
|||
groupBy: ruleParams.groupBy ?? DEFAULT_VALUES.GROUP_BY,
|
||||
termSize: ruleParams.termSize ?? DEFAULT_VALUES.TERM_SIZE,
|
||||
termField: ruleParams.termField,
|
||||
size: ruleParams.size ?? DEFAULT_VALUES.SIZE,
|
||||
size: ruleParams.size
|
||||
? ruleParams.size
|
||||
: isServerless
|
||||
? SERVERLESS_DEFAULT_VALUES.SIZE
|
||||
: DEFAULT_VALUES.SIZE,
|
||||
excludeHitsFromPreviousRun:
|
||||
ruleParams.excludeHitsFromPreviousRun ?? DEFAULT_VALUES.EXCLUDE_PREVIOUS_HITS,
|
||||
sourceFields: ruleParams.sourceFields,
|
||||
|
@ -381,7 +385,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
|||
onChangeWindowUnit={onChangeWindowUnit}
|
||||
onChangeSizeValue={onChangeSizeValue}
|
||||
errors={errors}
|
||||
hasValidationErrors={hasExpressionValidationErrors(props.ruleParams)}
|
||||
hasValidationErrors={hasExpressionValidationErrors(props.ruleParams, isServerless)}
|
||||
onTestFetch={onTestFetch}
|
||||
onCopyQuery={onCopyQuery}
|
||||
excludeHitsFromPreviousRun={ruleConfiguration.excludeHitsFromPreviousRun}
|
||||
|
|
|
@ -175,7 +175,7 @@ describe('expression params validation', () => {
|
|||
};
|
||||
expect(validateExpression(initialParams).errors.esQuery.length).toBeGreaterThan(0);
|
||||
expect(validateExpression(initialParams).errors.esQuery[0]).toBe(`Query field is required.`);
|
||||
expect(hasExpressionValidationErrors(initialParams)).toBe(true);
|
||||
expect(hasExpressionValidationErrors(initialParams, false)).toBe(true);
|
||||
});
|
||||
|
||||
test('if searchConfiguration property is not set should return proper error message', () => {
|
||||
|
@ -302,6 +302,25 @@ describe('expression params validation', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('if size property is > 100, in serverless, should return proper error message', () => {
|
||||
const initialParams: EsQueryRuleParams<SearchType.esQuery> = {
|
||||
index: ['test'],
|
||||
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n`,
|
||||
size: 250,
|
||||
timeWindowSize: 1,
|
||||
timeWindowUnit: 's',
|
||||
threshold: [0],
|
||||
timeField: '',
|
||||
excludeHitsFromPreviousRun: true,
|
||||
aggType: 'count',
|
||||
groupBy: 'all',
|
||||
};
|
||||
expect(validateExpression(initialParams, true).errors.size.length).toBeGreaterThan(0);
|
||||
expect(validateExpression(initialParams, true).errors.size[0]).toBe(
|
||||
'Size must be between 0 and 100.'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not return error messages if all is correct', () => {
|
||||
const initialParams: EsQueryRuleParams<SearchType.esQuery> = {
|
||||
index: ['test'],
|
||||
|
@ -316,7 +335,7 @@ describe('expression params validation', () => {
|
|||
groupBy: 'all',
|
||||
};
|
||||
expect(validateExpression(initialParams).errors.size.length).toBe(0);
|
||||
expect(hasExpressionValidationErrors(initialParams)).toBe(false);
|
||||
expect(hasExpressionValidationErrors(initialParams, false)).toBe(false);
|
||||
});
|
||||
|
||||
test('if esqlQuery property is not set should return proper error message', () => {
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
import {
|
||||
MAX_SELECTABLE_SOURCE_FIELDS,
|
||||
MAX_SELECTABLE_GROUP_BY_TERMS,
|
||||
ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS,
|
||||
ES_QUERY_MAX_HITS_PER_EXECUTION,
|
||||
} from '../../../common/constants';
|
||||
import { EsQueryRuleParams, SearchType } from './types';
|
||||
import { isEsqlQueryRule, isSearchSourceRule } from './util';
|
||||
|
@ -27,7 +29,7 @@ import {
|
|||
SEARCH_SOURCE_ONLY_EXPRESSION_ERRORS,
|
||||
} from './constants';
|
||||
|
||||
const validateCommonParams = (ruleParams: EsQueryRuleParams) => {
|
||||
const validateCommonParams = (ruleParams: EsQueryRuleParams, isServerless?: boolean) => {
|
||||
const {
|
||||
size,
|
||||
threshold,
|
||||
|
@ -144,11 +146,14 @@ const validateCommonParams = (ruleParams: EsQueryRuleParams) => {
|
|||
})
|
||||
);
|
||||
}
|
||||
if ((size && size < 0) || size > 10000) {
|
||||
const maxSize = isServerless
|
||||
? ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS
|
||||
: ES_QUERY_MAX_HITS_PER_EXECUTION;
|
||||
if ((size && size < 0) || size > maxSize) {
|
||||
errors.size.push(
|
||||
i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.invalidSizeRangeText', {
|
||||
defaultMessage: 'Size must be between 0 and {max, number}.',
|
||||
values: { max: 10000 },
|
||||
values: { max: maxSize },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -297,10 +302,13 @@ const validateEsqlQueryParams = (ruleParams: EsQueryRuleParams<SearchType.esqlQu
|
|||
return errors;
|
||||
};
|
||||
|
||||
export const validateExpression = (ruleParams: EsQueryRuleParams): ValidationResult => {
|
||||
export const validateExpression = (
|
||||
ruleParams: EsQueryRuleParams,
|
||||
isServerless?: boolean
|
||||
): ValidationResult => {
|
||||
const validationResult = { errors: {} };
|
||||
|
||||
const commonErrors = validateCommonParams(ruleParams);
|
||||
const commonErrors = validateCommonParams(ruleParams, isServerless);
|
||||
validationResult.errors = commonErrors;
|
||||
|
||||
/**
|
||||
|
@ -331,8 +339,11 @@ export const validateExpression = (ruleParams: EsQueryRuleParams): ValidationRes
|
|||
return validationResult;
|
||||
};
|
||||
|
||||
export const hasExpressionValidationErrors = (ruleParams: EsQueryRuleParams) => {
|
||||
const { errors: validationErrors } = validateExpression(ruleParams);
|
||||
export const hasExpressionValidationErrors = (
|
||||
ruleParams: EsQueryRuleParams,
|
||||
isServerless: boolean
|
||||
) => {
|
||||
const { errors: validationErrors } = validateExpression(ruleParams, isServerless);
|
||||
return Object.keys(validationErrors).some(
|
||||
(key) => validationErrors[key] && validationErrors[key].length
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { RegisterRuleTypesParams } from '../types';
|
||||
import { getRuleType } from './rule_type';
|
||||
|
||||
export function register(params: RegisterRuleTypesParams) {
|
||||
export function register(params: RegisterRuleTypesParams, isServerless: boolean) {
|
||||
const { alerting, core } = params;
|
||||
alerting.registerType(getRuleType(core));
|
||||
alerting.registerType(getRuleType(core, isServerless));
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_set
|
|||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
const coreSetup = coreMock.createSetup();
|
||||
const ruleType = getRuleType(coreSetup);
|
||||
let ruleType = getRuleType(coreSetup, false);
|
||||
const mockNow = jest.getRealSystemTime();
|
||||
|
||||
describe('ruleType', () => {
|
||||
|
@ -38,6 +38,7 @@ describe('ruleType', () => {
|
|||
});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
ruleType = getRuleType(coreSetup, false);
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
|
@ -161,6 +162,49 @@ describe('ruleType', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('validator succeeds with valid es query params (serverless)', async () => {
|
||||
ruleType = getRuleType(coreSetup, true);
|
||||
const params: Partial<Writable<OnlyEsQueryRuleParams>> = {
|
||||
index: ['index-name'],
|
||||
timeField: 'time-field',
|
||||
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
|
||||
size: 100,
|
||||
timeWindowSize: 5,
|
||||
timeWindowUnit: 'm',
|
||||
thresholdComparator: Comparator.LT,
|
||||
threshold: [0],
|
||||
searchType: 'esQuery',
|
||||
aggType: 'count',
|
||||
groupBy: 'all',
|
||||
};
|
||||
|
||||
expect(ruleType.validate.params.validate(params)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('validator fails with invalid es query params - size (serverless)', async () => {
|
||||
ruleType = getRuleType(coreSetup, true);
|
||||
const paramsSchema = ruleType.validate.params;
|
||||
if (!paramsSchema) throw new Error('params validator not set');
|
||||
|
||||
const params: Partial<Writable<OnlyEsQueryRuleParams>> = {
|
||||
index: ['index-name'],
|
||||
timeField: 'time-field',
|
||||
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
|
||||
size: 104,
|
||||
timeWindowSize: 5,
|
||||
timeWindowUnit: 'm',
|
||||
thresholdComparator: Comparator.LT,
|
||||
threshold: [0],
|
||||
searchType: 'esQuery',
|
||||
aggType: 'count',
|
||||
groupBy: 'all',
|
||||
};
|
||||
|
||||
expect(() => paramsSchema.validate(params)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[size]: must be less than or equal to 100"`
|
||||
);
|
||||
});
|
||||
|
||||
it('rule executor handles no documents returned by ES', async () => {
|
||||
const params: OnlyEsQueryRuleParams = {
|
||||
index: ['index-name'],
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
EsQueryRuleParamsExtractedParams,
|
||||
EsQueryRuleParamsSchema,
|
||||
EsQueryRuleState,
|
||||
validateServerless,
|
||||
} from './rule_type_params';
|
||||
import { ExecutorOptions } from './types';
|
||||
import { ActionGroupId } from './constants';
|
||||
|
@ -25,7 +26,8 @@ import { isSearchSourceRule } from './util';
|
|||
import { StackAlertType } from '../types';
|
||||
|
||||
export function getRuleType(
|
||||
core: CoreSetup
|
||||
core: CoreSetup,
|
||||
isServerless: boolean
|
||||
): RuleType<
|
||||
EsQueryRuleParams,
|
||||
EsQueryRuleParamsExtractedParams,
|
||||
|
@ -152,7 +154,15 @@ export function getRuleType(
|
|||
actionGroups: [{ id: ActionGroupId, name: actionGroupName }],
|
||||
defaultActionGroupId: ActionGroupId,
|
||||
validate: {
|
||||
params: EsQueryRuleParamsSchema,
|
||||
params: {
|
||||
validate: (object: unknown) => {
|
||||
const validated = EsQueryRuleParamsSchema.validate(object);
|
||||
if (isServerless) {
|
||||
validateServerless(validated);
|
||||
}
|
||||
return validated;
|
||||
},
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
params: {
|
||||
|
|
|
@ -9,11 +9,8 @@ import { TypeOf } from '@kbn/config-schema';
|
|||
import { MAX_GROUPS } from '@kbn/triggers-actions-ui-plugin/server';
|
||||
import type { Writable } from '@kbn/utility-types';
|
||||
import { Comparator } from '../../../common/comparator_types';
|
||||
import {
|
||||
EsQueryRuleParamsSchema,
|
||||
EsQueryRuleParams,
|
||||
ES_QUERY_MAX_HITS_PER_EXECUTION,
|
||||
} from './rule_type_params';
|
||||
import { ES_QUERY_MAX_HITS_PER_EXECUTION } from '../../../common';
|
||||
import { EsQueryRuleParamsSchema, EsQueryRuleParams, validateServerless } from './rule_type_params';
|
||||
|
||||
const DefaultParams: Writable<Partial<EsQueryRuleParams>> = {
|
||||
index: ['index-name'],
|
||||
|
@ -370,6 +367,15 @@ describe('ruleType Params validate()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('serverless', () => {
|
||||
it('fails for invalid size', async () => {
|
||||
params.size = 101;
|
||||
expect(onValidateServerless()).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[size]: must be less than or equal to 100"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('esqlQuery search type', () => {
|
||||
beforeEach(() => {
|
||||
params = { ...DefaultParams, searchType: 'esqlQuery', esqlQuery: { esql: 'from test' } };
|
||||
|
@ -402,4 +408,8 @@ describe('ruleType Params validate()', () => {
|
|||
function validate(): TypeOf<typeof EsQueryRuleParamsSchema> {
|
||||
return EsQueryRuleParamsSchema.validate(params);
|
||||
}
|
||||
|
||||
function onValidateServerless() {
|
||||
return () => validateServerless(params);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -19,13 +19,15 @@ import {
|
|||
MAX_SELECTABLE_SOURCE_FIELDS,
|
||||
MAX_SELECTABLE_GROUP_BY_TERMS,
|
||||
} from '../../../common/constants';
|
||||
import { ComparatorFnNames } from '../../../common';
|
||||
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';
|
||||
|
||||
export const ES_QUERY_MAX_HITS_PER_EXECUTION = 10000;
|
||||
|
||||
// rule type parameters
|
||||
export type EsQueryRuleParams = TypeOf<typeof EsQueryRuleParamsSchema>;
|
||||
export interface EsQueryRuleState extends RuleTypeState {
|
||||
|
@ -213,6 +215,20 @@ function validateParams(anyParams: unknown): string | undefined {
|
|||
}
|
||||
}
|
||||
|
||||
export function validateServerless(params: EsQueryRuleParams) {
|
||||
const { size } = params;
|
||||
if (size > ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.stackAlerts.esQuery.serverless.sizeErrorMessage', {
|
||||
defaultMessage: '[size]: must be less than or equal to {maxSize}',
|
||||
values: {
|
||||
maxSize: ES_QUERY_MAX_HITS_PER_EXECUTION_SERVERLESS,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateComparator(comparator: Comparator): string | undefined {
|
||||
if (ComparatorFnNames.has(comparator)) return;
|
||||
|
||||
|
|
|
@ -17,5 +17,5 @@ export function registerBuiltInRuleTypes(params: RegisterRuleTypesParams, isServ
|
|||
if (!isServerless) {
|
||||
registerGeoContainment(params);
|
||||
}
|
||||
registerEsQuery(params);
|
||||
registerEsQuery(params, isServerless);
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ const RuleAdd = <
|
|||
application: { capabilities },
|
||||
i18n: i18nStart,
|
||||
theme,
|
||||
isServerless,
|
||||
} = useKibana().services;
|
||||
|
||||
const canShowActions = hasShowActionsCapability(capabilities);
|
||||
|
@ -241,9 +242,10 @@ const RuleAdd = <
|
|||
} as Rule,
|
||||
ruleType,
|
||||
config,
|
||||
actionTypeRegistry
|
||||
actionTypeRegistry,
|
||||
isServerless
|
||||
),
|
||||
[rule, selectableConsumer, selectedConsumer, ruleType, config, actionTypeRegistry]
|
||||
[rule, selectableConsumer, selectedConsumer, ruleType, config, actionTypeRegistry, isServerless]
|
||||
);
|
||||
|
||||
// Confirm before saving if user is able to add actions but hasn't added any to this rule
|
||||
|
|
|
@ -143,6 +143,7 @@ export const RuleEdit = <
|
|||
notifications: { toasts },
|
||||
i18n: i18nStart,
|
||||
theme,
|
||||
isServerless,
|
||||
} = useKibana().services;
|
||||
|
||||
const setRule = (value: Rule) => {
|
||||
|
@ -183,7 +184,8 @@ export const RuleEdit = <
|
|||
rule as Rule,
|
||||
ruleType,
|
||||
config,
|
||||
actionTypeRegistry
|
||||
actionTypeRegistry,
|
||||
isServerless
|
||||
);
|
||||
|
||||
const checkForChangesAndCloseFlyout = () => {
|
||||
|
|
|
@ -131,10 +131,11 @@ export function getRuleErrors(
|
|||
rule: Rule,
|
||||
ruleTypeModel: RuleTypeModel | null,
|
||||
config: TriggersActionsUiConfig,
|
||||
actionTypeRegistry: ActionTypeRegistryContract
|
||||
actionTypeRegistry: ActionTypeRegistryContract,
|
||||
isServerless?: boolean
|
||||
) {
|
||||
const ruleParamsErrors: IErrorObject = ruleTypeModel
|
||||
? ruleTypeModel.validate(rule.params).errors
|
||||
? ruleTypeModel.validate(rule.params, isServerless).errors
|
||||
: {};
|
||||
|
||||
const ruleBaseErrors = validateBaseProperties(rule, config, actionTypeRegistry)
|
||||
|
|
|
@ -413,7 +413,7 @@ export interface RuleTypeModel<Params extends RuleTypeParams = RuleTypeParams> {
|
|||
description: string;
|
||||
iconClass: string;
|
||||
documentationUrl: string | ((docLinks: DocLinksStart) => string) | null;
|
||||
validate: (ruleParams: Params) => ValidationResult;
|
||||
validate: (ruleParams: Params, isServerless?: boolean) => ValidationResult;
|
||||
ruleParamsExpression:
|
||||
| React.FunctionComponent<any>
|
||||
| React.LazyExoticComponent<ComponentType<RuleTypeParamsExpressionProps<Params>>>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue