mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Alerting] Warn by default when rule schedule interval is set below the minimum configured. (#127498)
* Changing structure of minimumScheduleInterval config * Updating rules client logic to follow enforce flag * Updating UI to use enforce value * Updating config key in functional tests * Fixes * Fixes * Updating help text * Wording suggestsion from PR review * Log warning instead of throwing an error if rule has default interval less than minimum * Updating default interval to be minimum if minimum is greater than hardcoded default * Fixing checks * Fixing tests * Fixing tests * Fixing config * Fixing checks Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
bdecf1568e
commit
0b1425b974
56 changed files with 586 additions and 154 deletions
|
@ -195,12 +195,15 @@ For example, `20m`, `24h`, `7d`, `1w`. Default: `5m`.
|
|||
`xpack.alerting.cancelAlertsOnRuleTimeout`::
|
||||
Specifies whether to skip writing alerts and scheduling actions if rule execution is cancelled due to timeout. Default: `true`. This setting can be overridden by individual rule types.
|
||||
|
||||
`xpack.alerting.minimumScheduleInterval`::
|
||||
Specifies the minimum interval allowed for the all rules. This minimum is enforced for all rules created or updated after the introduction of this setting. The time is formatted as:
|
||||
`xpack.alerting.rules.minimumScheduleInterval.value`::
|
||||
Specifies the minimum schedule interval for rules. This minimum is applied to all rules created or updated after you set this value. The time is formatted as:
|
||||
+
|
||||
`<count>[s,m,h,d]`
|
||||
+
|
||||
For example, `20m`, `24h`, `7d`. Default: `1m`.
|
||||
|
||||
`xpack.alerting.rules.execution.actions.max`
|
||||
Specifies the maximum number of actions that a rule can trigger each time detection checks run.
|
||||
`xpack.alerting.rules.minimumScheduleInterval.enforce`::
|
||||
Specifies the behavior when a new or changed rule has a schedule interval less than the value defined in `xpack.alerting.rules.minimumScheduleInterval.value`. If `false`, rules with schedules less than the interval will be created but warnings will be logged. If `true`, rules with schedules less than the interval cannot be created. Default: `false`.
|
||||
|
||||
`xpack.alerting.rules.execution.actions.max`::
|
||||
Specifies the maximum number of actions that a rule can trigger each time detection checks run.
|
||||
|
|
|
@ -200,6 +200,8 @@ kibana_vars=(
|
|||
xpack.alerting.invalidateApiKeysTask.removalDelay
|
||||
xpack.alerting.defaultRuleTaskTimeout
|
||||
xpack.alerting.cancelAlertsOnRuleTimeout
|
||||
xpack.alerting.rules.minimumScheduleInterval.value
|
||||
xpack.alerting.rules.minimumScheduleInterval.enforce
|
||||
xpack.alerting.rules.execution.actions.max
|
||||
xpack.alerts.healthCheck.interval
|
||||
xpack.alerts.invalidateApiKeysTask.interval
|
||||
|
|
|
@ -22,13 +22,16 @@ describe('config validation', () => {
|
|||
"removalDelay": "1h",
|
||||
},
|
||||
"maxEphemeralActionsPerAlert": 10,
|
||||
"minimumScheduleInterval": "1m",
|
||||
"rules": Object {
|
||||
"execution": Object {
|
||||
"actions": Object {
|
||||
"max": 100000,
|
||||
},
|
||||
},
|
||||
"minimumScheduleInterval": Object {
|
||||
"enforce": false,
|
||||
"value": "1m",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -14,6 +14,13 @@ const ruleTypeSchema = schema.object({
|
|||
});
|
||||
|
||||
const rulesSchema = schema.object({
|
||||
minimumScheduleInterval: schema.object({
|
||||
value: schema.string({
|
||||
validate: validateDurationSchema,
|
||||
defaultValue: '1m',
|
||||
}),
|
||||
enforce: schema.boolean({ defaultValue: false }), // if enforce is false, only warnings will be shown
|
||||
}),
|
||||
execution: schema.object({
|
||||
timeout: schema.maybe(schema.string({ validate: validateDurationSchema })),
|
||||
actions: schema.object({
|
||||
|
@ -37,11 +44,10 @@ export const configSchema = schema.object({
|
|||
}),
|
||||
defaultRuleTaskTimeout: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }),
|
||||
cancelAlertsOnRuleTimeout: schema.boolean({ defaultValue: true }),
|
||||
minimumScheduleInterval: schema.string({ validate: validateDurationSchema, defaultValue: '1m' }),
|
||||
rules: rulesSchema,
|
||||
});
|
||||
|
||||
export type AlertingConfig = TypeOf<typeof configSchema>;
|
||||
export type PublicAlertingConfig = Pick<AlertingConfig, 'minimumScheduleInterval'>;
|
||||
export type RulesConfig = TypeOf<typeof rulesSchema>;
|
||||
export type RuleTypeConfig = Omit<RulesConfig, 'ruleTypeOverrides'>;
|
||||
export type RuleTypeConfig = Omit<RulesConfig, 'ruleTypeOverrides' | 'minimumScheduleInterval'>;
|
||||
export type AlertingRulesConfig = Pick<AlertingConfig['rules'], 'minimumScheduleInterval'>;
|
||||
|
|
|
@ -35,7 +35,7 @@ export type { FindResult } from './rules_client';
|
|||
export type { PublicAlert as Alert } from './alert';
|
||||
export { parseDuration } from './lib';
|
||||
export { getEsErrorMessage } from './lib/errors';
|
||||
export type { PublicAlertingConfig } from './config';
|
||||
export type { AlertingRulesConfig } from './config';
|
||||
export {
|
||||
ReadOperations,
|
||||
AlertingAuthorizationFilterType,
|
||||
|
|
|
@ -5,11 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getRulesConfig } from './get_rules_config';
|
||||
import { getExecutionConfigForRuleType } from './get_rules_config';
|
||||
import { RulesConfig } from '../config';
|
||||
|
||||
const ruleTypeId = 'test-rule-type-id';
|
||||
const config = {
|
||||
minimumScheduleInterval: {
|
||||
value: '2m',
|
||||
enforce: false,
|
||||
},
|
||||
execution: {
|
||||
timeout: '1m',
|
||||
actions: { max: 1000 },
|
||||
|
@ -17,6 +21,7 @@ const config = {
|
|||
} as RulesConfig;
|
||||
|
||||
const configWithRuleType = {
|
||||
...config,
|
||||
execution: {
|
||||
...config.execution,
|
||||
ruleTypeOverrides: [
|
||||
|
@ -30,7 +35,7 @@ const configWithRuleType = {
|
|||
|
||||
describe('get rules config', () => {
|
||||
test('returns the rule type specific config and keeps the default values that are not overwritten', () => {
|
||||
expect(getRulesConfig({ config: configWithRuleType, ruleTypeId })).toEqual({
|
||||
expect(getExecutionConfigForRuleType({ config: configWithRuleType, ruleTypeId })).toEqual({
|
||||
execution: {
|
||||
id: ruleTypeId,
|
||||
timeout: '1m',
|
||||
|
@ -40,6 +45,8 @@ describe('get rules config', () => {
|
|||
});
|
||||
|
||||
test('returns the default config when there is no rule type specific config', () => {
|
||||
expect(getRulesConfig({ config, ruleTypeId })).toEqual(config);
|
||||
expect(getExecutionConfigForRuleType({ config, ruleTypeId })).toEqual({
|
||||
execution: config.execution,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,21 +8,21 @@
|
|||
import { omit } from 'lodash';
|
||||
import { RulesConfig, RuleTypeConfig } from '../config';
|
||||
|
||||
export const getRulesConfig = ({
|
||||
export const getExecutionConfigForRuleType = ({
|
||||
config,
|
||||
ruleTypeId,
|
||||
}: {
|
||||
config: RulesConfig;
|
||||
ruleTypeId: string;
|
||||
}): RuleTypeConfig => {
|
||||
const ruleTypeConfig = config.execution.ruleTypeOverrides?.find(
|
||||
const ruleTypeExecutionConfig = config.execution.ruleTypeOverrides?.find(
|
||||
(ruleType) => ruleType.id === ruleTypeId
|
||||
);
|
||||
|
||||
return {
|
||||
execution: {
|
||||
...omit(config.execution, 'ruleTypeOverrides'),
|
||||
...ruleTypeConfig,
|
||||
...ruleTypeExecutionConfig,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -31,8 +31,8 @@ const generateAlertingConfig = (): AlertingConfig => ({
|
|||
maxEphemeralActionsPerAlert: 10,
|
||||
defaultRuleTaskTimeout: '5m',
|
||||
cancelAlertsOnRuleTimeout: true,
|
||||
minimumScheduleInterval: '1m',
|
||||
rules: {
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
execution: {
|
||||
actions: {
|
||||
max: 1000,
|
||||
|
@ -115,13 +115,16 @@ describe('Alerting Plugin', () => {
|
|||
|
||||
const setupContract = await plugin.setup(setupMocks, mockPlugins);
|
||||
|
||||
expect(setupContract.getConfig()).toEqual({ minimumScheduleInterval: '1m' });
|
||||
expect(setupContract.getConfig()).toEqual({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
});
|
||||
|
||||
it(`applies the default config if there is no rule type specific config `, async () => {
|
||||
const context = coreMock.createPluginInitializerContext<AlertingConfig>({
|
||||
...generateAlertingConfig(),
|
||||
rules: {
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
execution: {
|
||||
actions: {
|
||||
max: 123,
|
||||
|
@ -147,6 +150,7 @@ describe('Alerting Plugin', () => {
|
|||
const context = coreMock.createPluginInitializerContext<AlertingConfig>({
|
||||
...generateAlertingConfig(),
|
||||
rules: {
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
execution: {
|
||||
actions: { max: 123 },
|
||||
ruleTypeOverrides: [{ id: sampleRuleType.id, timeout: '1d' }],
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pick } from 'lodash';
|
||||
import { UsageCollectionSetup, UsageCounter } from 'src/plugins/usage_collection/server';
|
||||
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
|
||||
import {
|
||||
|
@ -57,12 +58,12 @@ import {
|
|||
scheduleApiKeyInvalidatorTask,
|
||||
} from './invalidate_pending_api_keys/task';
|
||||
import { scheduleAlertingHealthCheck, initializeAlertingHealth } from './health';
|
||||
import { AlertingConfig, PublicAlertingConfig } from './config';
|
||||
import { AlertingConfig, AlertingRulesConfig } from './config';
|
||||
import { getHealth } from './health/get_health';
|
||||
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
|
||||
import { AlertingAuthorization } from './authorization';
|
||||
import { getSecurityHealth, SecurityHealth } from './lib/get_security_health';
|
||||
import { getRulesConfig } from './lib/get_rules_config';
|
||||
import { getExecutionConfigForRuleType } from './lib/get_rules_config';
|
||||
|
||||
export const EVENT_LOG_PROVIDER = 'alerting';
|
||||
export const EVENT_LOG_ACTIONS = {
|
||||
|
@ -99,7 +100,7 @@ export interface PluginSetupContract {
|
|||
>
|
||||
): void;
|
||||
getSecurityHealth: () => Promise<SecurityHealth>;
|
||||
getConfig: () => PublicAlertingConfig;
|
||||
getConfig: () => AlertingRulesConfig;
|
||||
}
|
||||
|
||||
export interface PluginStartContract {
|
||||
|
@ -198,11 +199,12 @@ export class AlertingPlugin {
|
|||
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
|
||||
|
||||
const ruleTypeRegistry = new RuleTypeRegistry({
|
||||
logger: this.logger,
|
||||
taskManager: plugins.taskManager,
|
||||
taskRunnerFactory: this.taskRunnerFactory,
|
||||
licenseState: this.licenseState,
|
||||
licensing: plugins.licensing,
|
||||
minimumScheduleInterval: this.config.minimumScheduleInterval,
|
||||
minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
|
||||
});
|
||||
this.ruleTypeRegistry = ruleTypeRegistry;
|
||||
|
||||
|
@ -288,7 +290,7 @@ export class AlertingPlugin {
|
|||
if (!(ruleType.minimumLicenseRequired in LICENSE_TYPE)) {
|
||||
throw new Error(`"${ruleType.minimumLicenseRequired}" is not a valid license type`);
|
||||
}
|
||||
ruleType.config = getRulesConfig({
|
||||
ruleType.config = getExecutionConfigForRuleType({
|
||||
config: alertingConfig.rules,
|
||||
ruleTypeId: ruleType.id,
|
||||
});
|
||||
|
@ -310,9 +312,7 @@ export class AlertingPlugin {
|
|||
);
|
||||
},
|
||||
getConfig: () => {
|
||||
return {
|
||||
minimumScheduleInterval: alertingConfig.minimumScheduleInterval,
|
||||
};
|
||||
return pick(alertingConfig.rules, 'minimumScheduleInterval');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ export class AlertingPlugin {
|
|||
kibanaVersion: this.kibanaVersion,
|
||||
authorization: alertingAuthorizationClientFactory,
|
||||
eventLogger: this.eventLogger,
|
||||
minimumScheduleInterval: this.config.minimumScheduleInterval,
|
||||
minimumScheduleInterval: this.config.rules.minimumScheduleInterval,
|
||||
});
|
||||
|
||||
const getRulesClientWithRequest = (request: KibanaRequest) => {
|
||||
|
|
|
@ -12,6 +12,9 @@ import { taskManagerMock } from '../../task_manager/server/mocks';
|
|||
import { ILicenseState } from './lib/license_state';
|
||||
import { licenseStateMock } from './lib/license_state.mock';
|
||||
import { licensingMock } from '../../licensing/server/mocks';
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
let mockedLicenseState: jest.Mocked<ILicenseState>;
|
||||
let ruleTypeRegistryParams: ConstructorOptions;
|
||||
|
||||
|
@ -21,11 +24,12 @@ beforeEach(() => {
|
|||
jest.resetAllMocks();
|
||||
mockedLicenseState = licenseStateMock.create();
|
||||
ruleTypeRegistryParams = {
|
||||
logger,
|
||||
taskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(),
|
||||
licenseState: mockedLicenseState,
|
||||
licensing: licensingMock.createSetup(),
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -192,7 +196,32 @@ describe('Create Lifecycle', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('throws if defaultScheduleInterval is less than configured minimumScheduleInterval', () => {
|
||||
test('logs warning if defaultScheduleInterval is less than configured minimumScheduleInterval and enforce = false', () => {
|
||||
const ruleType: RuleType<never, never, never, never, never, 'default'> = {
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
defaultScheduleInterval: '10s',
|
||||
};
|
||||
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
|
||||
registry.register(ruleType);
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`Rule type "123" has a default interval of "10s", which is less than the configured minimum of "1m".`
|
||||
);
|
||||
});
|
||||
|
||||
test('logs warning and updates default if defaultScheduleInterval is less than configured minimumScheduleInterval and enforce = true', () => {
|
||||
const ruleType: RuleType<never, never, never, never, never, 'default'> = {
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
|
@ -209,17 +238,17 @@ describe('Create Lifecycle', () => {
|
|||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
defaultScheduleInterval: '10s',
|
||||
config: {
|
||||
execution: {
|
||||
actions: { max: 1000 },
|
||||
},
|
||||
},
|
||||
};
|
||||
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
|
||||
const registry = new RuleTypeRegistry({
|
||||
...ruleTypeRegistryParams,
|
||||
minimumScheduleInterval: { value: '1m', enforce: true },
|
||||
});
|
||||
registry.register(ruleType);
|
||||
|
||||
expect(() => registry.register(ruleType)).toThrowError(
|
||||
new Error(`Rule type \"123\" cannot specify a default interval less than 1m.`)
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`Rule type "123" cannot specify a default interval less than the configured minimum of "1m". "1m" will be used.`
|
||||
);
|
||||
expect(registry.get('123').defaultScheduleInterval).toEqual('1m');
|
||||
});
|
||||
|
||||
test('throws if RuleType action groups contains reserved group id', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import typeDetect from 'type-detect';
|
||||
import { intersection } from 'lodash';
|
||||
import { Logger } from 'kibana/server';
|
||||
import { LicensingPluginSetup } from '../../licensing/server';
|
||||
import { RunContext, TaskManagerSetupContract } from '../../task_manager/server';
|
||||
import { TaskRunnerFactory } from './task_runner';
|
||||
|
@ -30,13 +31,15 @@ import {
|
|||
} from '../common';
|
||||
import { ILicenseState } from './lib/license_state';
|
||||
import { getRuleTypeFeatureUsageName } from './lib/get_rule_type_feature_usage_name';
|
||||
import { AlertingRulesConfig } from '.';
|
||||
|
||||
export interface ConstructorOptions {
|
||||
logger: Logger;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
taskRunnerFactory: TaskRunnerFactory;
|
||||
licenseState: ILicenseState;
|
||||
licensing: LicensingPluginSetup;
|
||||
minimumScheduleInterval: string;
|
||||
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
}
|
||||
|
||||
export interface RegistryRuleType
|
||||
|
@ -126,20 +129,23 @@ export type UntypedNormalizedRuleType = NormalizedRuleType<
|
|||
>;
|
||||
|
||||
export class RuleTypeRegistry {
|
||||
private readonly logger: Logger;
|
||||
private readonly taskManager: TaskManagerSetupContract;
|
||||
private readonly ruleTypes: Map<string, UntypedNormalizedRuleType> = new Map();
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
private readonly licenseState: ILicenseState;
|
||||
private readonly minimumScheduleInterval: string;
|
||||
private readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
private readonly licensing: LicensingPluginSetup;
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
taskManager,
|
||||
taskRunnerFactory,
|
||||
licenseState,
|
||||
licensing,
|
||||
minimumScheduleInterval,
|
||||
}: ConstructorOptions) {
|
||||
this.logger = logger;
|
||||
this.taskManager = taskManager;
|
||||
this.taskRunnerFactory = taskRunnerFactory;
|
||||
this.licenseState = licenseState;
|
||||
|
@ -220,21 +226,18 @@ export class RuleTypeRegistry {
|
|||
}
|
||||
|
||||
const defaultIntervalInMs = parseDuration(ruleType.defaultScheduleInterval);
|
||||
const minimumIntervalInMs = parseDuration(this.minimumScheduleInterval);
|
||||
const minimumIntervalInMs = parseDuration(this.minimumScheduleInterval.value);
|
||||
if (defaultIntervalInMs < minimumIntervalInMs) {
|
||||
throw new Error(
|
||||
i18n.translate(
|
||||
'xpack.alerting.ruleTypeRegistry.register.defaultTimeoutTooShortRuleTypeError',
|
||||
{
|
||||
defaultMessage:
|
||||
'Rule type "{id}" cannot specify a default interval less than {minimumInterval}.',
|
||||
values: {
|
||||
id: ruleType.id,
|
||||
minimumInterval: this.minimumScheduleInterval,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
if (this.minimumScheduleInterval.enforce) {
|
||||
this.logger.warn(
|
||||
`Rule type "${ruleType.id}" cannot specify a default interval less than the configured minimum of "${this.minimumScheduleInterval.value}". "${this.minimumScheduleInterval.value}" will be used.`
|
||||
);
|
||||
ruleType.defaultScheduleInterval = this.minimumScheduleInterval.value;
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`Rule type "${ruleType.id}" has a default interval of "${ruleType.defaultScheduleInterval}", which is less than the configured minimum of "${this.minimumScheduleInterval.value}".`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ import {
|
|||
getModifiedSearch,
|
||||
modifyFilterKueryNode,
|
||||
} from './lib/mapped_params_utils';
|
||||
import { AlertingRulesConfig } from '../config';
|
||||
import {
|
||||
formatExecutionLogResult,
|
||||
getExecutionLogAggregation,
|
||||
|
@ -133,7 +134,7 @@ export interface ConstructorOptions {
|
|||
authorization: AlertingAuthorization;
|
||||
actionsAuthorization: ActionsAuthorization;
|
||||
ruleTypeRegistry: RuleTypeRegistry;
|
||||
minimumScheduleInterval: string;
|
||||
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
spaceId?: string;
|
||||
namespace?: string;
|
||||
|
@ -269,7 +270,7 @@ export class RulesClient {
|
|||
private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
private readonly authorization: AlertingAuthorization;
|
||||
private readonly ruleTypeRegistry: RuleTypeRegistry;
|
||||
private readonly minimumScheduleInterval: string;
|
||||
private readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
private readonly minimumScheduleIntervalInMs: number;
|
||||
private readonly createAPIKey: (name: string) => Promise<CreateAPIKeyResult>;
|
||||
private readonly getActionsClient: () => Promise<ActionsClient>;
|
||||
|
@ -311,7 +312,7 @@ export class RulesClient {
|
|||
this.taskManager = taskManager;
|
||||
this.ruleTypeRegistry = ruleTypeRegistry;
|
||||
this.minimumScheduleInterval = minimumScheduleInterval;
|
||||
this.minimumScheduleIntervalInMs = parseDuration(minimumScheduleInterval);
|
||||
this.minimumScheduleIntervalInMs = parseDuration(minimumScheduleInterval.value);
|
||||
this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient;
|
||||
this.authorization = authorization;
|
||||
this.createAPIKey = createAPIKey;
|
||||
|
@ -370,9 +371,16 @@ export class RulesClient {
|
|||
// Validate that schedule interval is not less than configured minimum
|
||||
const intervalInMs = parseDuration(data.schedule.interval);
|
||||
if (intervalInMs < this.minimumScheduleIntervalInMs) {
|
||||
throw Boom.badRequest(
|
||||
`Error creating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval}`
|
||||
);
|
||||
if (this.minimumScheduleInterval.enforce) {
|
||||
throw Boom.badRequest(
|
||||
`Error creating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval.value}`
|
||||
);
|
||||
} else {
|
||||
// just log warning but allow rule to be created
|
||||
this.logger.warn(
|
||||
`Rule schedule interval (${data.schedule.interval}) is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract saved object references for this rule
|
||||
|
@ -1112,9 +1120,16 @@ export class RulesClient {
|
|||
// Validate that schedule interval is not less than configured minimum
|
||||
const intervalInMs = parseDuration(data.schedule.interval);
|
||||
if (intervalInMs < this.minimumScheduleIntervalInMs) {
|
||||
throw Boom.badRequest(
|
||||
`Error updating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval}`
|
||||
);
|
||||
if (this.minimumScheduleInterval.enforce) {
|
||||
throw Boom.badRequest(
|
||||
`Error updating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval.value}`
|
||||
);
|
||||
} else {
|
||||
// just log warning but allow rule to be updated
|
||||
this.logger.warn(
|
||||
`Rule schedule interval (${data.schedule.interval}) is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract saved object references for this rule
|
||||
|
|
|
@ -33,7 +33,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
|
|
|
@ -52,7 +52,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -2546,7 +2546,73 @@ describe('create()', () => {
|
|||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws error when creating with an interval less than the minimum configured one', async () => {
|
||||
test('logs warning when creating with an interval less than the minimum configured one when enforce = false', async () => {
|
||||
const data = getMockData({ schedule: { interval: '1s' } });
|
||||
ruleTypeRegistry.get.mockImplementation(() => ({
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
useSavedObjectReferences: {
|
||||
extractReferences: jest.fn(),
|
||||
injectReferences: jest.fn(),
|
||||
},
|
||||
}));
|
||||
const createdAttributes = {
|
||||
...data,
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '1s' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: createdAttributes,
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await rulesClient.create({ data });
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith(
|
||||
`Rule schedule interval (1s) is less than the minimum value (1m). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.`
|
||||
);
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalled();
|
||||
expect(taskManager.schedule).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws error when creating with an interval less than the minimum configured one when enforce = true', async () => {
|
||||
rulesClient = new RulesClient({
|
||||
...rulesClientParams,
|
||||
minimumScheduleInterval: { value: '1m', enforce: true },
|
||||
});
|
||||
ruleTypeRegistry.get.mockImplementation(() => ({
|
||||
id: '123',
|
||||
name: 'Test',
|
||||
|
|
|
@ -30,7 +30,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
|
|
|
@ -42,7 +42,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -36,7 +36,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -37,7 +37,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -35,7 +35,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -34,7 +34,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -40,7 +40,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -41,7 +41,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -38,7 +38,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -34,7 +34,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -34,7 +34,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -35,7 +35,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -34,7 +34,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -34,7 +34,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -54,7 +54,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1809,7 +1809,154 @@ describe('update()', () => {
|
|||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws error when updating with an interval less than the minimum configured one', async () => {
|
||||
test('logs warning when creating with an interval less than the minimum configured one when enforce = false', async () => {
|
||||
actionsClient.getBulk.mockReset();
|
||||
actionsClient.getBulk.mockResolvedValue([
|
||||
{
|
||||
id: '1',
|
||||
actionTypeId: 'test',
|
||||
config: {
|
||||
from: 'me@me.com',
|
||||
hasAuth: false,
|
||||
host: 'hello',
|
||||
port: 22,
|
||||
secure: null,
|
||||
service: null,
|
||||
},
|
||||
isMissingSecrets: false,
|
||||
name: 'email connector',
|
||||
isPreconfigured: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
actionTypeId: 'test2',
|
||||
config: {
|
||||
from: 'me@me.com',
|
||||
hasAuth: false,
|
||||
host: 'hello',
|
||||
port: 22,
|
||||
secure: null,
|
||||
service: null,
|
||||
},
|
||||
isMissingSecrets: false,
|
||||
name: 'email connector',
|
||||
isPreconfigured: false,
|
||||
},
|
||||
]);
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
enabled: true,
|
||||
schedule: { interval: '1m' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_1',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_2',
|
||||
actionTypeId: 'test2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
notifyWhen: 'onActiveAlert',
|
||||
scheduledTaskId: 'task-123',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: 'action_2',
|
||||
type: 'action',
|
||||
id: '2',
|
||||
},
|
||||
],
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'api_key_pending_invalidation',
|
||||
attributes: {
|
||||
apiKeyId: '234',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
await rulesClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
schedule: { interval: '1s' },
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith(
|
||||
`Rule schedule interval (1s) is less than the minimum value (1m). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.`
|
||||
);
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws error when updating with an interval less than the minimum configured one when enforce = true', async () => {
|
||||
rulesClient = new RulesClient({
|
||||
...rulesClientParams,
|
||||
minimumScheduleInterval: { value: '1m', enforce: true },
|
||||
});
|
||||
await expect(
|
||||
rulesClient.update({
|
||||
id: '1',
|
||||
|
|
|
@ -35,7 +35,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
|
|
|
@ -52,7 +52,7 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
};
|
||||
|
||||
// this suite consists of two suites running tests against mutable RulesClient APIs:
|
||||
|
|
|
@ -35,7 +35,7 @@ const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(
|
|||
const securityPluginSetup = securityMock.createSetup();
|
||||
const securityPluginStart = securityMock.createStart();
|
||||
|
||||
const alertsAuthorization = alertingAuthorizationMock.create();
|
||||
const alertingAuthorization = alertingAuthorizationMock.create();
|
||||
const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory();
|
||||
|
||||
const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
|
||||
|
@ -44,7 +44,7 @@ const rulesClientFactoryParams: jest.Mocked<RulesClientFactoryOpts> = {
|
|||
ruleTypeRegistry: ruleTypeRegistryMock.create(),
|
||||
getSpaceId: jest.fn(),
|
||||
spaceIdToNamespace: jest.fn(),
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
|
||||
actions: actionsMock.createStart(),
|
||||
eventLog: eventLogMock.createStart(),
|
||||
|
@ -82,14 +82,14 @@ beforeEach(() => {
|
|||
rulesClientFactoryParams.spaceIdToNamespace.mockReturnValue('default');
|
||||
});
|
||||
|
||||
test('creates an alerts client with proper constructor arguments when security is enabled', async () => {
|
||||
test('creates a rules client with proper constructor arguments when security is enabled', async () => {
|
||||
const factory = new RulesClientFactory();
|
||||
factory.initialize({ securityPluginSetup, securityPluginStart, ...rulesClientFactoryParams });
|
||||
const request = KibanaRequest.from(fakeRequest);
|
||||
|
||||
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
|
||||
alertingAuthorizationClientFactory.create.mockReturnValue(
|
||||
alertsAuthorization as unknown as AlertingAuthorization
|
||||
alertingAuthorization as unknown as AlertingAuthorization
|
||||
);
|
||||
|
||||
factory.create(request, savedObjectsService);
|
||||
|
@ -107,7 +107,7 @@ test('creates an alerts client with proper constructor arguments when security i
|
|||
|
||||
expect(jest.requireMock('./rules_client').RulesClient).toHaveBeenCalledWith({
|
||||
unsecuredSavedObjectsClient: savedObjectsClient,
|
||||
authorization: alertsAuthorization,
|
||||
authorization: alertingAuthorization,
|
||||
actionsAuthorization,
|
||||
logger: rulesClientFactoryParams.logger,
|
||||
taskManager: rulesClientFactoryParams.taskManager,
|
||||
|
@ -120,18 +120,18 @@ test('creates an alerts client with proper constructor arguments when security i
|
|||
createAPIKey: expect.any(Function),
|
||||
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
|
||||
kibanaVersion: '7.10.0',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
});
|
||||
|
||||
test('creates an alerts client with proper constructor arguments', async () => {
|
||||
test('creates a rules client with proper constructor arguments', async () => {
|
||||
const factory = new RulesClientFactory();
|
||||
factory.initialize(rulesClientFactoryParams);
|
||||
const request = KibanaRequest.from(fakeRequest);
|
||||
|
||||
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
|
||||
alertingAuthorizationClientFactory.create.mockReturnValue(
|
||||
alertsAuthorization as unknown as AlertingAuthorization
|
||||
alertingAuthorization as unknown as AlertingAuthorization
|
||||
);
|
||||
|
||||
factory.create(request, savedObjectsService);
|
||||
|
@ -145,7 +145,7 @@ test('creates an alerts client with proper constructor arguments', async () => {
|
|||
|
||||
expect(jest.requireMock('./rules_client').RulesClient).toHaveBeenCalledWith({
|
||||
unsecuredSavedObjectsClient: savedObjectsClient,
|
||||
authorization: alertsAuthorization,
|
||||
authorization: alertingAuthorization,
|
||||
actionsAuthorization,
|
||||
logger: rulesClientFactoryParams.logger,
|
||||
taskManager: rulesClientFactoryParams.taskManager,
|
||||
|
@ -158,7 +158,7 @@ test('creates an alerts client with proper constructor arguments', async () => {
|
|||
getActionsClient: expect.any(Function),
|
||||
getEventLogClient: expect.any(Function),
|
||||
kibanaVersion: '7.10.0',
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/serve
|
|||
import { TaskManagerStartContract } from '../../task_manager/server';
|
||||
import { IEventLogClientService, IEventLogger } from '../../../plugins/event_log/server';
|
||||
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
|
||||
import { AlertingRulesConfig } from './config';
|
||||
export interface RulesClientFactoryOpts {
|
||||
logger: Logger;
|
||||
taskManager: TaskManagerStartContract;
|
||||
|
@ -33,7 +34,7 @@ export interface RulesClientFactoryOpts {
|
|||
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
|
||||
authorization: AlertingAuthorizationClientFactory;
|
||||
eventLogger?: IEventLogger;
|
||||
minimumScheduleInterval: string;
|
||||
minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
}
|
||||
|
||||
export class RulesClientFactory {
|
||||
|
@ -51,7 +52,7 @@ export class RulesClientFactory {
|
|||
private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version'];
|
||||
private authorization!: AlertingAuthorizationClientFactory;
|
||||
private eventLogger?: IEventLogger;
|
||||
private minimumScheduleInterval!: string;
|
||||
private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval'];
|
||||
|
||||
public initialize(options: RulesClientFactoryOpts) {
|
||||
if (this.isInitialized) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { ILicenseState } from '../lib/license_state';
|
|||
import { licenseStateMock } from '../lib/license_state.mock';
|
||||
import { licensingMock } from '../../../licensing/server/mocks';
|
||||
import { isRuleExportable } from './is_rule_exportable';
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
|
||||
let ruleTypeRegistryParams: ConstructorOptions;
|
||||
let logger: MockedLogger;
|
||||
|
@ -24,11 +25,12 @@ beforeEach(() => {
|
|||
mockedLicenseState = licenseStateMock.create();
|
||||
logger = loggerMock.create();
|
||||
ruleTypeRegistryParams = {
|
||||
logger: loggingSystemMock.create().get(),
|
||||
taskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(),
|
||||
licenseState: mockedLicenseState,
|
||||
licensing: licensingMock.createSetup(),
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ import {
|
|||
AlertExecutionStatusWarningReasons,
|
||||
} from '../common';
|
||||
import { LicenseType } from '../../licensing/server';
|
||||
import { RulesConfig } from './config';
|
||||
import { RuleTypeConfig } from './config';
|
||||
export type WithoutQueryAndParams<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>;
|
||||
export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined;
|
||||
|
||||
|
@ -170,7 +170,7 @@ export interface RuleType<
|
|||
ruleTaskTimeout?: string;
|
||||
cancelAlertsOnRuleTimeout?: boolean;
|
||||
doesSetRecoveryContext?: boolean;
|
||||
config?: RulesConfig;
|
||||
config?: RuleTypeConfig;
|
||||
}
|
||||
export type UntypedRuleType = RuleType<
|
||||
AlertTypeParams,
|
||||
|
|
|
@ -135,7 +135,7 @@ describe('alert_form', () => {
|
|||
<KibanaReactContext.Provider>
|
||||
<RuleForm
|
||||
rule={initialAlert}
|
||||
config={{ minimumScheduleInterval: '1m' }}
|
||||
config={{ minimumScheduleInterval: { value: '1m', enforce: false } }}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], 'schedule.interval': [] }}
|
||||
operation="create"
|
||||
|
|
|
@ -37,4 +37,4 @@ export enum SORT_ORDERS {
|
|||
|
||||
export const DEFAULT_SEARCH_PAGE_SIZE: number = 10;
|
||||
|
||||
export const DEFAULT_ALERT_INTERVAL = '1m';
|
||||
export const DEFAULT_RULE_INTERVAL = '1m';
|
||||
|
|
|
@ -23,6 +23,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
authorized_consumers: authorizedConsumers,
|
||||
rule_task_timeout: ruleTaskTimeout,
|
||||
does_set_recovery_context: doesSetRecoveryContext,
|
||||
default_schedule_interval: defaultScheduleInterval,
|
||||
...rest
|
||||
}: AsApiContract<RuleType>) => ({
|
||||
enabledInLicense,
|
||||
|
@ -34,6 +35,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
authorizedConsumers,
|
||||
ruleTaskTimeout,
|
||||
doesSetRecoveryContext,
|
||||
defaultScheduleInterval,
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { getInitialInterval } from './get_initial_interval';
|
||||
import { DEFAULT_RULE_INTERVAL } from '../../constants';
|
||||
|
||||
describe('getInitialInterval', () => {
|
||||
test('should return DEFAULT_RULE_INTERVAL if minimumScheduleInterval is undefined', () => {
|
||||
expect(getInitialInterval()).toEqual(DEFAULT_RULE_INTERVAL);
|
||||
});
|
||||
|
||||
test('should return DEFAULT_RULE_INTERVAL if minimumScheduleInterval is smaller than or equal to default', () => {
|
||||
expect(getInitialInterval('1m')).toEqual(DEFAULT_RULE_INTERVAL);
|
||||
});
|
||||
|
||||
test('should return minimumScheduleInterval if minimumScheduleInterval is greater than default', () => {
|
||||
expect(getInitialInterval('5m')).toEqual('5m');
|
||||
});
|
||||
});
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DEFAULT_RULE_INTERVAL } from '../../constants';
|
||||
import { parseDuration } from '../../../../../alerting/common';
|
||||
|
||||
export function getInitialInterval(minimumScheduleInterval?: string) {
|
||||
if (minimumScheduleInterval) {
|
||||
// return minimum schedule interval if it is larger than the default
|
||||
if (parseDuration(minimumScheduleInterval) > parseDuration(DEFAULT_RULE_INTERVAL)) {
|
||||
return minimumScheduleInterval;
|
||||
}
|
||||
}
|
||||
return DEFAULT_RULE_INTERVAL;
|
||||
}
|
|
@ -27,6 +27,7 @@ import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
|||
import { ReactWrapper } from 'enzyme';
|
||||
import { ALERTS_FEATURE_ID } from '../../../../../alerting/common';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { triggersActionsUiConfig } from '../../../common/lib/config_api';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
|
@ -40,7 +41,7 @@ jest.mock('../../lib/rule_api', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../../../common/lib/config_api', () => ({
|
||||
triggersActionsUiConfig: jest.fn().mockResolvedValue({ minimumScheduleInterval: '1m' }),
|
||||
triggersActionsUiConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/lib/health_api', () => ({
|
||||
|
@ -175,6 +176,9 @@ describe('rule_add', () => {
|
|||
}
|
||||
|
||||
it('renders rule add flyout', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
await setup({}, onClose);
|
||||
|
||||
|
@ -194,6 +198,9 @@ describe('rule_add', () => {
|
|||
});
|
||||
|
||||
it('renders rule add flyout with initial values', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
await setup(
|
||||
{
|
||||
|
@ -207,13 +214,33 @@ describe('rule_add', () => {
|
|||
);
|
||||
|
||||
expect(wrapper.find('input#ruleName').props().value).toBe('Simple status rule');
|
||||
|
||||
expect(wrapper.find('[data-test-subj="tagsComboBox"]').first().text()).toBe('uptimelogs');
|
||||
expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(1);
|
||||
expect(wrapper.find('[data-test-subj="intervalInputUnit"]').first().props().value).toBe('h');
|
||||
});
|
||||
|
||||
expect(wrapper.find('.euiSelect').first().props().value).toBe('h');
|
||||
it('renders rule add flyout with DEFAULT_RULE_INTERVAL if no initialValues specified and no minimumScheduleInterval', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({});
|
||||
await setup();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(1);
|
||||
expect(wrapper.find('[data-test-subj="intervalInputUnit"]').first().props().value).toBe('m');
|
||||
});
|
||||
|
||||
it('renders rule add flyout with minimumScheduleInterval if minimumScheduleInterval is greater than DEFAULT_RULE_INTERVAL', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '5m', enforce: false },
|
||||
});
|
||||
await setup();
|
||||
|
||||
expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(5);
|
||||
expect(wrapper.find('[data-test-subj="intervalInputUnit"]').first().props().value).toBe('m');
|
||||
});
|
||||
|
||||
it('emit an onClose event when the rule is saved', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
const onClose = jest.fn();
|
||||
const rule = mockRule();
|
||||
|
||||
|
@ -242,7 +269,10 @@ describe('rule_add', () => {
|
|||
expect(onClose).toHaveBeenCalledWith(RuleFlyoutCloseReason.SAVED);
|
||||
});
|
||||
|
||||
it('should enforce any default inteval', async () => {
|
||||
it('should enforce any default interval', async () => {
|
||||
(triggersActionsUiConfig as jest.Mock).mockResolvedValue({
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
await setup({ ruleTypeId: 'my-rule-type' }, jest.fn(), '3h');
|
||||
|
||||
// Wait for handlers to fire
|
||||
|
|
|
@ -33,8 +33,9 @@ import { HealthContextProvider } from '../../context/health_context';
|
|||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { hasRuleChanged, haveRuleParamsChanged } from './has_rule_changed';
|
||||
import { getRuleWithInvalidatedFields } from '../../lib/value_validators';
|
||||
import { DEFAULT_ALERT_INTERVAL } from '../../constants';
|
||||
import { DEFAULT_RULE_INTERVAL } from '../../constants';
|
||||
import { triggersActionsUiConfig } from '../../../common/lib/config_api';
|
||||
import { getInitialInterval } from './get_initial_interval';
|
||||
|
||||
const RuleAdd = ({
|
||||
consumer,
|
||||
|
@ -58,7 +59,7 @@ const RuleAdd = ({
|
|||
consumer,
|
||||
ruleTypeId,
|
||||
schedule: {
|
||||
interval: DEFAULT_ALERT_INTERVAL,
|
||||
interval: DEFAULT_RULE_INTERVAL,
|
||||
},
|
||||
actions: [],
|
||||
tags: [],
|
||||
|
@ -146,6 +147,14 @@ const RuleAdd = ({
|
|||
})();
|
||||
}, [rule, actionTypeRegistry]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config.minimumScheduleInterval && !initialValues?.schedule?.interval) {
|
||||
setRuleProperty('schedule', {
|
||||
interval: getInitialInterval(config.minimumScheduleInterval.value),
|
||||
});
|
||||
}
|
||||
}, [config.minimumScheduleInterval, initialValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (rule.ruleTypeId && ruleTypeIndex) {
|
||||
const type = ruleTypeIndex.get(rule.ruleTypeId);
|
||||
|
@ -156,7 +165,7 @@ const RuleAdd = ({
|
|||
}, [rule.ruleTypeId, ruleTypeIndex, rule.schedule.interval, changedFromDefaultInterval]);
|
||||
|
||||
useEffect(() => {
|
||||
if (rule.schedule.interval !== DEFAULT_ALERT_INTERVAL && !changedFromDefaultInterval) {
|
||||
if (rule.schedule.interval !== DEFAULT_RULE_INTERVAL && !changedFromDefaultInterval) {
|
||||
setChangedFromDefaultInterval(true);
|
||||
}
|
||||
}, [rule.schedule.interval, changedFromDefaultInterval]);
|
||||
|
|
|
@ -36,7 +36,9 @@ jest.mock('../../lib/rule_api', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../../../common/lib/config_api', () => ({
|
||||
triggersActionsUiConfig: jest.fn().mockResolvedValue({ minimumScheduleInterval: '1m' }),
|
||||
triggersActionsUiConfig: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }),
|
||||
}));
|
||||
|
||||
jest.mock('./rule_errors', () => ({
|
||||
|
@ -229,6 +231,6 @@ describe('rule_edit', () => {
|
|||
await setup();
|
||||
const lastCall = getRuleErrors.mock.calls[getRuleErrors.mock.calls.length - 1];
|
||||
expect(lastCall[2]).toBeDefined();
|
||||
expect(lastCall[2]).toEqual({ minimumScheduleInterval: '1m' });
|
||||
expect(lastCall[2]).toEqual({ minimumScheduleInterval: { value: '1m', enforce: false } });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { Rule, RuleTypeModel } from '../../../types';
|
||||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
|
||||
const config = { minimumScheduleInterval: '1m' };
|
||||
const config = { minimumScheduleInterval: { value: '1m', enforce: false } };
|
||||
describe('rule_errors', () => {
|
||||
describe('validateBaseProperties()', () => {
|
||||
it('should validate the name', () => {
|
||||
|
@ -44,10 +44,24 @@ describe('rule_errors', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should validate the minimumScheduleInterval', () => {
|
||||
it('should validate the minimumScheduleInterval if enforce = false', () => {
|
||||
const rule = mockRule();
|
||||
rule.schedule.interval = '2s';
|
||||
const result = validateBaseProperties(rule, config);
|
||||
expect(result.errors).toStrictEqual({
|
||||
name: [],
|
||||
'schedule.interval': [],
|
||||
ruleTypeId: [],
|
||||
actionConnectors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate the minimumScheduleInterval if enforce = true', () => {
|
||||
const rule = mockRule();
|
||||
rule.schedule.interval = '2s';
|
||||
const result = validateBaseProperties(rule, {
|
||||
minimumScheduleInterval: { value: '1m', enforce: true },
|
||||
});
|
||||
expect(result.errors).toStrictEqual({
|
||||
name: [],
|
||||
'schedule.interval': ['Interval must be at least 1 minute.'],
|
||||
|
|
|
@ -43,15 +43,15 @@ export function validateBaseProperties(
|
|||
defaultMessage: 'Check interval is required.',
|
||||
})
|
||||
);
|
||||
} else if (config.minimumScheduleInterval) {
|
||||
} else if (config.minimumScheduleInterval && config.minimumScheduleInterval.enforce) {
|
||||
const duration = parseDuration(ruleObject.schedule.interval);
|
||||
const minimumDuration = parseDuration(config.minimumScheduleInterval);
|
||||
const minimumDuration = parseDuration(config.minimumScheduleInterval.value);
|
||||
if (duration < minimumDuration) {
|
||||
errors['schedule.interval'].push(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.ruleForm.error.belowMinimumText', {
|
||||
defaultMessage: 'Interval must be at least {minimum}.',
|
||||
values: {
|
||||
minimum: formatDuration(config.minimumScheduleInterval, true),
|
||||
minimum: formatDuration(config.minimumScheduleInterval.value, true),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('rule_form', () => {
|
|||
describe('rule_form create rule', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
async function setup() {
|
||||
async function setup(enforceMinimum = false, schedule = '1m') {
|
||||
const mocks = coreMock.createSetup();
|
||||
const { useLoadRuleTypes } = jest.requireMock('../../hooks/use_load_rule_types');
|
||||
const ruleTypes: RuleType[] = [
|
||||
|
@ -174,7 +174,7 @@ describe('rule_form', () => {
|
|||
params: {},
|
||||
consumer: ALERTS_FEATURE_ID,
|
||||
schedule: {
|
||||
interval: '1m',
|
||||
interval: schedule,
|
||||
},
|
||||
actions: [],
|
||||
tags: [],
|
||||
|
@ -186,7 +186,7 @@ describe('rule_form', () => {
|
|||
wrapper = mountWithIntl(
|
||||
<RuleForm
|
||||
rule={initialRule}
|
||||
config={{ minimumScheduleInterval: '1m' }}
|
||||
config={{ minimumScheduleInterval: { value: '1m', enforce: enforceMinimum } }}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], 'schedule.interval': [], ruleTypeId: [] }}
|
||||
operation="create"
|
||||
|
@ -214,13 +214,27 @@ describe('rule_form', () => {
|
|||
expect(ruleTypeSelectOptions.exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders minimum schedule interval', async () => {
|
||||
await setup();
|
||||
it('renders minimum schedule interval helper text when enforce = true', async () => {
|
||||
await setup(true);
|
||||
expect(wrapper.find('[data-test-subj="intervalFormRow"]').first().prop('helpText')).toEqual(
|
||||
`Interval must be at least 1 minute.`
|
||||
);
|
||||
});
|
||||
|
||||
it('renders minimum schedule interval helper suggestion when enforce = false and schedule is less than configuration', async () => {
|
||||
await setup(false, '10s');
|
||||
expect(wrapper.find('[data-test-subj="intervalFormRow"]').first().prop('helpText')).toEqual(
|
||||
`Intervals less than 1 minute are not recommended due to performance considerations.`
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render minimum schedule interval helper when enforce = false and schedule is greater than configuration', async () => {
|
||||
await setup();
|
||||
expect(wrapper.find('[data-test-subj="intervalFormRow"]').first().prop('helpText')).toEqual(
|
||||
``
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render registered rule type which non editable', async () => {
|
||||
await setup();
|
||||
const ruleTypeSelectOptions = wrapper.find(
|
||||
|
@ -368,7 +382,7 @@ describe('rule_form', () => {
|
|||
wrapper = mountWithIntl(
|
||||
<RuleForm
|
||||
rule={initialRule}
|
||||
config={{ minimumScheduleInterval: '1m' }}
|
||||
config={{ minimumScheduleInterval: { value: '1m', enforce: false } }}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], 'schedule.interval': [], ruleTypeId: [] }}
|
||||
operation="create"
|
||||
|
@ -431,7 +445,7 @@ describe('rule_form', () => {
|
|||
wrapper = mountWithIntl(
|
||||
<RuleForm
|
||||
rule={initialRule}
|
||||
config={{ minimumScheduleInterval: '1m' }}
|
||||
config={{ minimumScheduleInterval: { value: '1m', enforce: false } }}
|
||||
dispatch={() => {}}
|
||||
errors={{ name: [], 'schedule.interval': [], ruleTypeId: [] }}
|
||||
operation="create"
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
formatDuration,
|
||||
getDurationNumberInItsUnit,
|
||||
getDurationUnitValue,
|
||||
parseDuration,
|
||||
} from '../../../../../alerting/common/parse_duration';
|
||||
import { RuleReducerAction, InitialRule } from './rule_reducer';
|
||||
import {
|
||||
|
@ -73,8 +74,8 @@ import { checkRuleTypeEnabled } from '../../lib/check_rule_type_enabled';
|
|||
import { ruleTypeCompare, ruleTypeGroupCompare } from '../../lib/rule_type_compare';
|
||||
import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
|
||||
import { SectionLoading } from '../../components/section_loading';
|
||||
import { DEFAULT_ALERT_INTERVAL } from '../../constants';
|
||||
import { useLoadRuleTypes } from '../../hooks/use_load_rule_types';
|
||||
import { getInitialInterval } from './get_initial_interval';
|
||||
|
||||
const ENTER_KEY = 13;
|
||||
|
||||
|
@ -97,9 +98,6 @@ interface RuleFormProps<MetaData = Record<string, any>> {
|
|||
filteredSolutions?: string[] | undefined;
|
||||
}
|
||||
|
||||
const defaultScheduleInterval = getDurationNumberInItsUnit(DEFAULT_ALERT_INTERVAL);
|
||||
const defaultScheduleIntervalUnit = getDurationUnitValue(DEFAULT_ALERT_INTERVAL);
|
||||
|
||||
export const RuleForm = ({
|
||||
rule,
|
||||
config,
|
||||
|
@ -126,6 +124,10 @@ export const RuleForm = ({
|
|||
|
||||
const [ruleTypeModel, setRuleTypeModel] = useState<RuleTypeModel | null>(null);
|
||||
|
||||
const defaultRuleInterval = getInitialInterval(config.minimumScheduleInterval?.value);
|
||||
const defaultScheduleInterval = getDurationNumberInItsUnit(defaultRuleInterval);
|
||||
const defaultScheduleIntervalUnit = getDurationUnitValue(defaultRuleInterval);
|
||||
|
||||
const [ruleInterval, setRuleInterval] = useState<number | undefined>(
|
||||
rule.schedule.interval
|
||||
? getDurationNumberInItsUnit(rule.schedule.interval)
|
||||
|
@ -238,15 +240,10 @@ export const RuleForm = ({
|
|||
if (rule.schedule.interval) {
|
||||
const interval = getDurationNumberInItsUnit(rule.schedule.interval);
|
||||
const intervalUnit = getDurationUnitValue(rule.schedule.interval);
|
||||
|
||||
if (interval !== defaultScheduleInterval) {
|
||||
setRuleInterval(interval);
|
||||
}
|
||||
if (intervalUnit !== defaultScheduleIntervalUnit) {
|
||||
setRuleIntervalUnit(intervalUnit);
|
||||
}
|
||||
setRuleInterval(interval);
|
||||
setRuleIntervalUnit(intervalUnit);
|
||||
}
|
||||
}, [rule.schedule.interval]);
|
||||
}, [rule.schedule.interval, defaultScheduleInterval, defaultScheduleIntervalUnit]);
|
||||
|
||||
const setRuleProperty = useCallback(
|
||||
<Key extends keyof Rule>(key: Key, value: Rule[Key] | null) => {
|
||||
|
@ -588,12 +585,50 @@ export const RuleForm = ({
|
|||
type="questionInCircle"
|
||||
content={i18n.translate('xpack.triggersActionsUI.sections.ruleForm.checkWithTooltip', {
|
||||
defaultMessage:
|
||||
'Define how often to evaluate the condition. Checks are queued; they run as close to the defined value as capacity allows. The xpack.alerting.minimumScheduleInterval setting defines the minimum value.',
|
||||
'Define how often to evaluate the condition. Checks are queued; they run as close to the defined value as capacity allows. The xpack.alerting.rules.minimumScheduleInterval.value setting defines the minimum value. The xpack.alerting.rules.minimumScheduleInterval.enforce setting defines whether this minimum is required or suggested.',
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const getHelpTextForInterval = () => {
|
||||
if (!config || !config.minimumScheduleInterval) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// No help text if there is an error
|
||||
if (errors['schedule.interval'].length > 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (config.minimumScheduleInterval.enforce) {
|
||||
// Always show help text if minimum is enforced
|
||||
return i18n.translate('xpack.triggersActionsUI.sections.ruleForm.checkEveryHelpText', {
|
||||
defaultMessage: 'Interval must be at least {minimum}.',
|
||||
values: {
|
||||
minimum: formatDuration(config.minimumScheduleInterval.value, true),
|
||||
},
|
||||
});
|
||||
} else if (
|
||||
rule.schedule.interval &&
|
||||
parseDuration(rule.schedule.interval) < parseDuration(config.minimumScheduleInterval.value)
|
||||
) {
|
||||
// Only show help text if current interval is less than suggested
|
||||
return i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleForm.checkEveryHelpSuggestionText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Intervals less than {minimum} are not recommended due to performance considerations.',
|
||||
values: {
|
||||
minimum: formatDuration(config.minimumScheduleInterval.value, true),
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiForm>
|
||||
<EuiFlexGrid columns={2}>
|
||||
|
@ -669,16 +704,7 @@ export const RuleForm = ({
|
|||
fullWidth
|
||||
data-test-subj="intervalFormRow"
|
||||
display="rowCompressed"
|
||||
helpText={
|
||||
errors['schedule.interval'].length > 0
|
||||
? ''
|
||||
: i18n.translate('xpack.triggersActionsUI.sections.ruleForm.checkEveryHelpText', {
|
||||
defaultMessage: 'Interval must be at least {minimum}.',
|
||||
values: {
|
||||
minimum: formatDuration(config.minimumScheduleInterval ?? '1m', true),
|
||||
},
|
||||
})
|
||||
}
|
||||
helpText={getHelpTextForInterval()}
|
||||
label={labelForRuleChecked}
|
||||
isInvalid={errors['schedule.interval'].length > 0}
|
||||
error={errors['schedule.interval']}
|
||||
|
|
|
@ -41,7 +41,9 @@ jest.mock('../../../../common/lib/health_api', () => ({
|
|||
triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })),
|
||||
}));
|
||||
jest.mock('../../../../common/lib/config_api', () => ({
|
||||
triggersActionsUiConfig: jest.fn().mockResolvedValue({ minimumScheduleInterval: '1m' }),
|
||||
triggersActionsUiConfig: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }),
|
||||
}));
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: () => ({
|
||||
|
|
|
@ -349,5 +349,8 @@ export enum Percentiles {
|
|||
}
|
||||
|
||||
export interface TriggersActionsUiConfig {
|
||||
minimumScheduleInterval?: string;
|
||||
minimumScheduleInterval?: {
|
||||
value: string;
|
||||
enforce: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('createConfigRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
const logger = loggingSystemMock.create().get();
|
||||
createConfigRoute(logger, router, `/api/triggers_actions_ui`, {
|
||||
minimumScheduleInterval: '1m',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
});
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
@ -24,7 +24,7 @@ describe('createConfigRoute', () => {
|
|||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
expect(mockResponse.ok.mock.calls[0][0]).toEqual({
|
||||
body: { minimumScheduleInterval: '1m' },
|
||||
body: { minimumScheduleInterval: { value: '1m', enforce: false } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,13 +13,13 @@ import {
|
|||
KibanaResponseFactory,
|
||||
} from 'kibana/server';
|
||||
import { Logger } from '../../../../../src/core/server';
|
||||
import { PublicAlertingConfig } from '../../../alerting/server';
|
||||
import { AlertingRulesConfig } from '../../../alerting/server';
|
||||
|
||||
export function createConfigRoute(
|
||||
logger: Logger,
|
||||
router: IRouter,
|
||||
baseRoute: string,
|
||||
config?: PublicAlertingConfig
|
||||
config?: AlertingRulesConfig
|
||||
) {
|
||||
const path = `${baseRoute}/_config`;
|
||||
logger.debug(`registering triggers_actions_ui config route GET ${path}`);
|
||||
|
|
|
@ -162,7 +162,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
|
||||
'--xpack.alerting.invalidateApiKeysTask.interval="15s"',
|
||||
'--xpack.alerting.healthCheck.interval="1s"',
|
||||
'--xpack.alerting.minimumScheduleInterval="1s"',
|
||||
'--xpack.alerting.rules.minimumScheduleInterval.value="1s"',
|
||||
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
|
||||
`--xpack.actions.rejectUnauthorized=${rejectUnauthorized}`,
|
||||
`--xpack.actions.microsoftGraphApiUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}/api/_actions-FTS-external-service-simulators/exchange/users/test@/sendMail`,
|
||||
|
|
|
@ -58,7 +58,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
'--logging.loggers[2].name=http.server.response',
|
||||
'--logging.loggers[2].level=all',
|
||||
`--logging.loggers[2].appenders=${JSON.stringify(['file'])}`,
|
||||
`--xpack.alerting.minimumScheduleInterval="1s"`,
|
||||
`--xpack.alerting.rules.minimumScheduleInterval.value="1s"`,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -66,7 +66,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
`--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
|
||||
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
`--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`,
|
||||
`--xpack.alerting.minimumScheduleInterval="1s"`,
|
||||
`--xpack.alerting.rules.minimumScheduleInterval.value="1s"`,
|
||||
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
|
||||
`--xpack.actions.preconfiguredAlertHistoryEsIndex=false`,
|
||||
`--xpack.actions.preconfigured=${JSON.stringify({
|
||||
|
|
|
@ -78,7 +78,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
`--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`,
|
||||
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
|
||||
`--xpack.alerting.minimumScheduleInterval="1s"`,
|
||||
`--xpack.alerting.rules.minimumScheduleInterval.value="1s"`,
|
||||
'--xpack.eventLog.logEntries=true',
|
||||
...disabledPlugins
|
||||
.filter((k) => k !== 'security')
|
||||
|
|
|
@ -46,7 +46,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
'--xpack.ruleRegistry.unsafe.indexUpgrade.enabled=true',
|
||||
// Without below line, default interval for rules is 1m
|
||||
// See https://github.com/elastic/kibana/pull/125396 for details
|
||||
'--xpack.alerting.minimumScheduleInterval=1s',
|
||||
'--xpack.alerting.rules.minimumScheduleInterval.value=1s',
|
||||
'--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true',
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||
'riskyHostsEnabled',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue