[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:
Alexi Doak 2024-04-15 09:09:26 -07:00 committed by GitHub
parent e7e5105965
commit f447ed09c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 180 additions and 48 deletions

View file

@ -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;

View file

@ -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';

View file

@ -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>(),

View file

@ -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(

View file

@ -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>

View file

@ -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,

View file

@ -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}

View file

@ -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', () => {

View file

@ -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
);

View file

@ -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));
}

View file

@ -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'],

View file

@ -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: {

View file

@ -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);
}
});

View file

@ -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;

View file

@ -17,5 +17,5 @@ export function registerBuiltInRuleTypes(params: RegisterRuleTypesParams, isServ
if (!isServerless) {
registerGeoContainment(params);
}
registerEsQuery(params);
registerEsQuery(params, isServerless);
}

View file

@ -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

View file

@ -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 = () => {

View file

@ -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)

View file

@ -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>>>;