mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[RAM] Improve rule interval circuit breaker error message (#168173)
## Summary Improve rule interval circuit breaker error message to the following endpoints: - create - update - bulk edit - bulk enable ### Bulk modification  ### Modifying a single rule  ### Checklist - [x] [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 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
This commit is contained in:
parent
e76e589cb4
commit
c66a86b07e
27 changed files with 536 additions and 119 deletions
|
@ -18,18 +18,24 @@ export const RuleStatusDropdownSandbox = ({ triggersActionsUi }: SandboxProps) =
|
|||
const [isSnoozedUntil, setIsSnoozedUntil] = useState<Date | null>(null);
|
||||
const [muteAll, setMuteAll] = useState(false);
|
||||
|
||||
const onEnableRule: any = async () => {
|
||||
setEnabled(true);
|
||||
setMuteAll(false);
|
||||
setIsSnoozedUntil(null);
|
||||
};
|
||||
|
||||
const onDisableRule: any = async () => {
|
||||
setEnabled(false);
|
||||
};
|
||||
|
||||
return triggersActionsUi.getRuleStatusDropdown({
|
||||
rule: {
|
||||
enabled,
|
||||
isSnoozedUntil,
|
||||
muteAll,
|
||||
},
|
||||
enableRule: async () => {
|
||||
setEnabled(true);
|
||||
setMuteAll(false);
|
||||
setIsSnoozedUntil(null);
|
||||
},
|
||||
disableRule: async () => setEnabled(false),
|
||||
enableRule: onEnableRule,
|
||||
disableRule: onDisableRule,
|
||||
snoozeRule: async (schedule) => {
|
||||
if (schedule.duration === -1) {
|
||||
setIsSnoozedUntil(null);
|
||||
|
|
|
@ -37,6 +37,7 @@ export * from './rrule_type';
|
|||
export * from './rule_tags_aggregation';
|
||||
export * from './iso_weekdays';
|
||||
export * from './saved_objects/rules/mappings';
|
||||
export * from './rule_circuit_breaker_error_message';
|
||||
|
||||
export type {
|
||||
MaintenanceWindowModificationMetadata,
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 {
|
||||
getRuleCircuitBreakerErrorMessage,
|
||||
parseRuleCircuitBreakerErrorMessage,
|
||||
} from './rule_circuit_breaker_error_message';
|
||||
|
||||
describe('getRuleCircuitBreakerErrorMessage', () => {
|
||||
it('should return the correct message', () => {
|
||||
expect(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name: 'test rule',
|
||||
action: 'create',
|
||||
interval: 5,
|
||||
intervalAvailable: 4,
|
||||
})
|
||||
).toMatchInlineSnapshot(
|
||||
`"Error validating circuit breaker - Rule 'test rule' cannot be created. The maximum number of runs per minute would be exceeded. - The rule has 5 runs per minute; there are only 4 runs per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals."`
|
||||
);
|
||||
|
||||
expect(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name: 'test rule',
|
||||
action: 'update',
|
||||
interval: 1,
|
||||
intervalAvailable: 1,
|
||||
})
|
||||
).toMatchInlineSnapshot(
|
||||
`"Error validating circuit breaker - Rule 'test rule' cannot be updated. The maximum number of runs per minute would be exceeded. - The rule has 1 run per minute; there is only 1 run per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals."`
|
||||
);
|
||||
|
||||
expect(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name: 'test rule',
|
||||
action: 'bulkEdit',
|
||||
interval: 1,
|
||||
intervalAvailable: 1,
|
||||
rules: 5,
|
||||
})
|
||||
).toMatchInlineSnapshot(
|
||||
`"Error validating circuit breaker - Rules cannot be bulk edited. The maximum number of runs per minute would be exceeded. - The rules have 1 run per minute; there is only 1 run per minute available. Before you can modify these rules, you must disable other rules or change their check intervals so they run less frequently."`
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse the error message', () => {
|
||||
const message = getRuleCircuitBreakerErrorMessage({
|
||||
name: 'test rule',
|
||||
action: 'create',
|
||||
interval: 5,
|
||||
intervalAvailable: 4,
|
||||
});
|
||||
|
||||
const parsedMessage = parseRuleCircuitBreakerErrorMessage(message);
|
||||
|
||||
expect(parsedMessage.summary).toContain("Rule 'test rule' cannot be created");
|
||||
expect(parsedMessage.details).toContain('The rule has 5 runs per minute');
|
||||
});
|
||||
|
||||
it('should passthrough the message if it is not related to circuit breakers', () => {
|
||||
const parsedMessage = parseRuleCircuitBreakerErrorMessage('random message');
|
||||
|
||||
expect(parsedMessage.summary).toEqual('random message');
|
||||
expect(parsedMessage.details).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const errorMessageHeader = 'Error validating circuit breaker';
|
||||
|
||||
const getCreateRuleErrorSummary = (name: string) => {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.createSummary', {
|
||||
defaultMessage: `Rule '{name}' cannot be created. The maximum number of runs per minute would be exceeded.`,
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getUpdateRuleErrorSummary = (name: string) => {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.updateSummary', {
|
||||
defaultMessage: `Rule '{name}' cannot be updated. The maximum number of runs per minute would be exceeded.`,
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getEnableRuleErrorSummary = (name: string) => {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.enableSummary', {
|
||||
defaultMessage: `Rule '{name}' cannot be enabled. The maximum number of runs per minute would be exceeded.`,
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getBulkEditRuleErrorSummary = () => {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.bulkEditSummary', {
|
||||
defaultMessage: `Rules cannot be bulk edited. The maximum number of runs per minute would be exceeded.`,
|
||||
});
|
||||
};
|
||||
|
||||
const getBulkEnableRuleErrorSummary = () => {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.bulkEnableSummary', {
|
||||
defaultMessage: `Rules cannot be bulk enabled. The maximum number of runs per minute would be exceeded.`,
|
||||
});
|
||||
};
|
||||
|
||||
const getRuleCircuitBreakerErrorDetail = ({
|
||||
interval,
|
||||
intervalAvailable,
|
||||
rules,
|
||||
}: {
|
||||
interval: number;
|
||||
intervalAvailable: number;
|
||||
rules: number;
|
||||
}) => {
|
||||
if (rules === 1) {
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.ruleDetail', {
|
||||
defaultMessage: `The rule has {interval, plural, one {{interval} run} other {{interval} runs}} per minute; there {intervalAvailable, plural, one {is only {intervalAvailable} run} other {are only {intervalAvailable} runs}} per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals.`,
|
||||
values: {
|
||||
interval,
|
||||
intervalAvailable,
|
||||
},
|
||||
});
|
||||
}
|
||||
return i18n.translate('xpack.alerting.ruleCircuitBreaker.error.multipleRuleDetail', {
|
||||
defaultMessage: `The rules have {interval, plural, one {{interval} run} other {{interval} runs}} per minute; there {intervalAvailable, plural, one {is only {intervalAvailable} run} other {are only {intervalAvailable} runs}} per minute available. Before you can modify these rules, you must disable other rules or change their check intervals so they run less frequently.`,
|
||||
values: {
|
||||
interval,
|
||||
intervalAvailable,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getRuleCircuitBreakerErrorMessage = ({
|
||||
name = '',
|
||||
interval,
|
||||
intervalAvailable,
|
||||
action,
|
||||
rules = 1,
|
||||
}: {
|
||||
name?: string;
|
||||
interval: number;
|
||||
intervalAvailable: number;
|
||||
action: 'update' | 'create' | 'enable' | 'bulkEdit' | 'bulkEnable';
|
||||
rules?: number;
|
||||
}) => {
|
||||
let errorMessageSummary: string;
|
||||
|
||||
switch (action) {
|
||||
case 'update':
|
||||
errorMessageSummary = getUpdateRuleErrorSummary(name);
|
||||
break;
|
||||
case 'create':
|
||||
errorMessageSummary = getCreateRuleErrorSummary(name);
|
||||
break;
|
||||
case 'enable':
|
||||
errorMessageSummary = getEnableRuleErrorSummary(name);
|
||||
break;
|
||||
case 'bulkEdit':
|
||||
errorMessageSummary = getBulkEditRuleErrorSummary();
|
||||
break;
|
||||
case 'bulkEnable':
|
||||
errorMessageSummary = getBulkEnableRuleErrorSummary();
|
||||
break;
|
||||
}
|
||||
|
||||
return `Error validating circuit breaker - ${errorMessageSummary} - ${getRuleCircuitBreakerErrorDetail(
|
||||
{
|
||||
interval,
|
||||
intervalAvailable,
|
||||
rules,
|
||||
}
|
||||
)}`;
|
||||
};
|
||||
|
||||
export const parseRuleCircuitBreakerErrorMessage = (
|
||||
message: string
|
||||
): {
|
||||
summary: string;
|
||||
details?: string;
|
||||
} => {
|
||||
if (!message.includes(errorMessageHeader)) {
|
||||
return {
|
||||
summary: message,
|
||||
};
|
||||
}
|
||||
const segments = message.split(' - ');
|
||||
return {
|
||||
summary: segments[1],
|
||||
details: segments[2],
|
||||
};
|
||||
};
|
|
@ -25,7 +25,7 @@ import {
|
|||
convertRuleIdsToKueryNode,
|
||||
} from '../../../../lib';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
|
||||
import { parseDuration } from '../../../../../common/parse_duration';
|
||||
import { parseDuration, getRuleCircuitBreakerErrorMessage } from '../../../../../common';
|
||||
import { bulkMarkApiKeysForInvalidation } from '../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
|
||||
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
|
||||
import {
|
||||
|
@ -77,7 +77,7 @@ import {
|
|||
transformRuleDomainToRuleAttributes,
|
||||
transformRuleDomainToRule,
|
||||
} from '../../transforms';
|
||||
import { validateScheduleLimit } from '../get_schedule_frequency';
|
||||
import { validateScheduleLimit, ValidateScheduleLimitResult } from '../get_schedule_frequency';
|
||||
|
||||
const isValidInterval = (interval: string | undefined): interval is string => {
|
||||
return interval !== undefined;
|
||||
|
@ -326,15 +326,16 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
|
|||
.map((rule) => rule.attributes.schedule?.interval)
|
||||
.filter(isValidInterval);
|
||||
|
||||
try {
|
||||
if (operations.some((operation) => operation.field === 'schedule')) {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval,
|
||||
updatedInterval,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
let validationPayload: ValidateScheduleLimitResult = null;
|
||||
if (operations.some((operation) => operation.field === 'schedule')) {
|
||||
validationPayload = await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval,
|
||||
updatedInterval,
|
||||
});
|
||||
}
|
||||
|
||||
if (validationPayload) {
|
||||
return {
|
||||
apiKeysToInvalidate: Array.from(apiKeysMap.values())
|
||||
.filter((value) => value.newApiKey)
|
||||
|
@ -342,7 +343,13 @@ async function bulkEditRulesOcc<Params extends RuleParams>(
|
|||
resultSavedObjects: [],
|
||||
rules: [],
|
||||
errors: rules.map((rule) => ({
|
||||
message: `Failed to bulk edit rule - ${error.message}`,
|
||||
message: getRuleCircuitBreakerErrorMessage({
|
||||
name: rule.attributes.name || 'n/a',
|
||||
interval: validationPayload!.interval,
|
||||
intervalAvailable: validationPayload!.intervalAvailable,
|
||||
action: 'bulkEdit',
|
||||
rules: updatedInterval.length,
|
||||
}),
|
||||
rule: {
|
||||
id: rule.id,
|
||||
name: rule.attributes.name || 'n/a',
|
||||
|
|
|
@ -8,7 +8,7 @@ import Semver from 'semver';
|
|||
import Boom from '@hapi/boom';
|
||||
import { SavedObject, SavedObjectsUtils } from '@kbn/core/server';
|
||||
import { withSpan } from '@kbn/apm-utils';
|
||||
import { parseDuration } from '../../../../../common/parse_duration';
|
||||
import { parseDuration, getRuleCircuitBreakerErrorMessage } from '../../../../../common';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
|
||||
import {
|
||||
validateRuleTypeParams,
|
||||
|
@ -36,7 +36,7 @@ import { RuleAttributes } from '../../../../data/rule/types';
|
|||
import type { CreateRuleData } from './types';
|
||||
import { createRuleDataSchema } from './schemas';
|
||||
import { createRuleSavedObject } from '../../../../rules_client/lib';
|
||||
import { validateScheduleLimit } from '../get_schedule_frequency';
|
||||
import { validateScheduleLimit, ValidateScheduleLimitResult } from '../get_schedule_frequency';
|
||||
|
||||
export interface CreateRuleOptions {
|
||||
id?: string;
|
||||
|
@ -61,16 +61,29 @@ export async function createRule<Params extends RuleParams = never>(
|
|||
|
||||
try {
|
||||
createRuleDataSchema.validate(data);
|
||||
if (data.enabled) {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: data.schedule.interval,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating create data - ${error.message}`);
|
||||
}
|
||||
|
||||
let validationPayload: ValidateScheduleLimitResult = null;
|
||||
if (data.enabled) {
|
||||
validationPayload = await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: data.schedule.interval,
|
||||
});
|
||||
}
|
||||
|
||||
if (validationPayload) {
|
||||
throw Boom.badRequest(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name: data.name,
|
||||
interval: validationPayload!.interval,
|
||||
intervalAvailable: validationPayload!.intervalAvailable,
|
||||
action: 'create',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await withSpan({ name: 'authorization.ensureAuthorized', type: 'rules' }, () =>
|
||||
context.authorization.ensureAuthorized({
|
||||
|
|
|
@ -183,53 +183,55 @@ describe('validateScheduleLimit', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should not throw if the updated interval does not exceed limits', () => {
|
||||
return expect(
|
||||
validateScheduleLimit({
|
||||
test('should not return anything if the updated interval does not exceed limits', async () => {
|
||||
expect(
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: ['1m', '1m'],
|
||||
})
|
||||
).resolves.toBe(undefined);
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('should throw if the updated interval exceeds limits', () => {
|
||||
return expect(
|
||||
validateScheduleLimit({
|
||||
test('should return interval if the updated interval exceeds limits', async () => {
|
||||
expect(
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: ['1m', '1m', '1m', '2m'],
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Run limit reached: The rule has 3.5 runs per minute; there are only 3 runs per minute available."`
|
||||
);
|
||||
).toEqual({
|
||||
interval: 3.5,
|
||||
intervalAvailable: 3,
|
||||
});
|
||||
});
|
||||
|
||||
test('should not throw if previous interval was modified to be under the limit', () => {
|
||||
test('should not return anything if previous interval was modified to be under the limit', async () => {
|
||||
internalSavedObjectsRepository.find.mockResolvedValue(
|
||||
getMockAggregationResult([{ interval: '1m', count: 6 }])
|
||||
);
|
||||
|
||||
return expect(
|
||||
validateScheduleLimit({
|
||||
expect(
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval: ['1m', '1m'],
|
||||
updatedInterval: ['2m', '2m'],
|
||||
})
|
||||
).resolves.toBe(undefined);
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('should throw if the previous interval was modified to exceed the limit', () => {
|
||||
test('should return interval if the previous interval was modified to exceed the limit', async () => {
|
||||
internalSavedObjectsRepository.find.mockResolvedValue(
|
||||
getMockAggregationResult([{ interval: '1m', count: 5 }])
|
||||
);
|
||||
|
||||
return expect(
|
||||
validateScheduleLimit({
|
||||
expect(
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval: ['1m'],
|
||||
updatedInterval: ['30s'],
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Run limit reached: The rule has 2 runs per minute; there are only 1 runs per minute available."`
|
||||
);
|
||||
).toEqual({
|
||||
interval: 2,
|
||||
intervalAvailable: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -85,7 +85,11 @@ interface ValidateScheduleLimitParams {
|
|||
updatedInterval: string | string[];
|
||||
}
|
||||
|
||||
export const validateScheduleLimit = async (params: ValidateScheduleLimitParams) => {
|
||||
export type ValidateScheduleLimitResult = { interval: number; intervalAvailable: number } | null;
|
||||
|
||||
export const validateScheduleLimit = async (
|
||||
params: ValidateScheduleLimitParams
|
||||
): Promise<ValidateScheduleLimitResult> => {
|
||||
const { context, prevInterval = [], updatedInterval = [] } = params;
|
||||
|
||||
const prevIntervalArray = Array.isArray(prevInterval) ? prevInterval : [prevInterval];
|
||||
|
@ -108,8 +112,11 @@ export const validateScheduleLimit = async (params: ValidateScheduleLimitParams)
|
|||
const computedRemainingSchedulesPerMinute = remainingSchedulesPerMinute + prevSchedulePerMinute;
|
||||
|
||||
if (computedRemainingSchedulesPerMinute < updatedSchedulesPerMinute) {
|
||||
throw new Error(
|
||||
`Run limit reached: The rule has ${updatedSchedulesPerMinute} runs per minute; there are only ${computedRemainingSchedulesPerMinute} runs per minute available.`
|
||||
);
|
||||
return {
|
||||
interval: updatedSchedulesPerMinute,
|
||||
intervalAvailable: remainingSchedulesPerMinute,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -7,4 +7,6 @@
|
|||
|
||||
export type { GetScheduleFrequencyResult } from './types';
|
||||
|
||||
export type { ValidateScheduleLimitResult } from './get_schedule_frequency';
|
||||
|
||||
export { getScheduleFrequency, validateScheduleLimit } from './get_schedule_frequency';
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
buildKueryNodeFilter,
|
||||
getAndValidateCommonBulkOptions,
|
||||
} from '../common';
|
||||
import { getRuleCircuitBreakerErrorMessage } from '../../../common';
|
||||
import {
|
||||
getAuthorizationFilter,
|
||||
checkAuthorizationAndGetTotal,
|
||||
|
@ -143,13 +144,18 @@ const bulkEnableRulesWithOCC = async (
|
|||
.filter((rule) => !rule.attributes.enabled)
|
||||
.map((rule) => rule.attributes.schedule?.interval);
|
||||
|
||||
try {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval,
|
||||
const validationPayload = await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval,
|
||||
});
|
||||
|
||||
if (validationPayload) {
|
||||
scheduleValidationError = getRuleCircuitBreakerErrorMessage({
|
||||
interval: validationPayload.interval,
|
||||
intervalAvailable: validationPayload.intervalAvailable,
|
||||
action: 'bulkEnable',
|
||||
rules: updatedInterval.length,
|
||||
});
|
||||
} catch (error) {
|
||||
scheduleValidationError = `Error validating enable rule data - ${error.message}`;
|
||||
}
|
||||
|
||||
await pMap(rulesFinderRules, async (rule) => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
|||
import { RulesClientContext } from '../types';
|
||||
import { updateMeta, createNewAPIKeySet, scheduleTask, migrateLegacyActions } from '../lib';
|
||||
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
|
||||
import { getRuleCircuitBreakerErrorMessage } from '../../../common';
|
||||
|
||||
export async function enable(context: RulesClientContext, { id }: { id: string }): Promise<void> {
|
||||
return await retryIfConflicts(
|
||||
|
@ -48,13 +49,20 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string }
|
|||
references = alert.references;
|
||||
}
|
||||
|
||||
try {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: attributes.schedule.interval,
|
||||
});
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating enable rule data - ${error.message}`);
|
||||
const validationPayload = await validateScheduleLimit({
|
||||
context,
|
||||
updatedInterval: attributes.schedule.interval,
|
||||
});
|
||||
|
||||
if (validationPayload) {
|
||||
throw Boom.badRequest(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name: attributes.name,
|
||||
interval: validationPayload.interval,
|
||||
intervalAvailable: validationPayload.intervalAvailable,
|
||||
action: 'enable',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../types';
|
||||
import { validateRuleTypeParams, getRuleNotifyWhenType } from '../../lib';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
|
||||
import { parseDuration } from '../../../common/parse_duration';
|
||||
import { parseDuration, getRuleCircuitBreakerErrorMessage } from '../../../common';
|
||||
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
|
||||
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
|
||||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
||||
|
@ -33,7 +33,10 @@ import {
|
|||
createNewAPIKeySet,
|
||||
migrateLegacyActions,
|
||||
} from '../lib';
|
||||
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
|
||||
import {
|
||||
validateScheduleLimit,
|
||||
ValidateScheduleLimitResult,
|
||||
} from '../../application/rule/methods/get_schedule_frequency';
|
||||
|
||||
type ShouldIncrementRevision = (params?: RuleTypeParams) => boolean;
|
||||
|
||||
|
@ -90,18 +93,27 @@ async function updateWithOCC<Params extends RuleTypeParams>(
|
|||
}
|
||||
|
||||
const {
|
||||
attributes: { enabled, schedule },
|
||||
attributes: { enabled, schedule, name },
|
||||
} = alertSavedObject;
|
||||
try {
|
||||
if (enabled && schedule.interval !== data.schedule.interval) {
|
||||
await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval: alertSavedObject.attributes.schedule?.interval,
|
||||
updatedInterval: data.schedule.interval,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating update data - ${error.message}`);
|
||||
|
||||
let validationPayload: ValidateScheduleLimitResult = null;
|
||||
if (enabled && schedule.interval !== data.schedule.interval) {
|
||||
validationPayload = await validateScheduleLimit({
|
||||
context,
|
||||
prevInterval: alertSavedObject.attributes.schedule?.interval,
|
||||
updatedInterval: data.schedule.interval,
|
||||
});
|
||||
}
|
||||
|
||||
if (validationPayload) {
|
||||
throw Boom.badRequest(
|
||||
getRuleCircuitBreakerErrorMessage({
|
||||
name,
|
||||
interval: validationPayload.interval,
|
||||
intervalAvailable: validationPayload.intervalAvailable,
|
||||
action: 'update',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
const seeFullErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.toastWithCircuitBreaker.seeFullError',
|
||||
{
|
||||
defaultMessage: 'See full error',
|
||||
}
|
||||
);
|
||||
|
||||
const hideFullErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.components.toastWithCircuitBreaker.hideFullError',
|
||||
{
|
||||
defaultMessage: 'Hide full error',
|
||||
}
|
||||
);
|
||||
|
||||
export const ToastWithCircuitBreakerContent: React.FC = ({ children }) => {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
const onToggleShowDetails = useCallback(() => {
|
||||
setShowDetails((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showDetails && (
|
||||
<>
|
||||
<EuiText size="s">{children}</EuiText>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" color="danger" onClick={onToggleShowDetails}>
|
||||
{showDetails ? hideFullErrorMessage : seeFullErrorMessage}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -51,7 +51,7 @@ jest.mock('../../../../common/lib/kibana', () => ({
|
|||
}));
|
||||
|
||||
const mockAPIs = {
|
||||
bulkEnableRules: jest.fn(),
|
||||
bulkEnableRules: jest.fn().mockResolvedValue({ errors: [] }),
|
||||
bulkDisableRules: jest.fn(),
|
||||
snoozeRule: jest.fn(),
|
||||
unsnoozeRule: jest.fn(),
|
||||
|
@ -170,7 +170,6 @@ describe('rule status panel', () => {
|
|||
|
||||
it('should enable the rule when picking enable in the dropdown', async () => {
|
||||
const rule = mockRule({ enabled: false });
|
||||
const bulkEnableRules = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleStatusPanelWithProvider
|
||||
{...mockAPIs}
|
||||
|
@ -179,7 +178,6 @@ describe('rule status panel', () => {
|
|||
healthColor="primary"
|
||||
statusMessage="Ok"
|
||||
requestRefresh={requestRefresh}
|
||||
bulkEnableRules={bulkEnableRules}
|
||||
/>
|
||||
);
|
||||
const actionsElem = wrapper
|
||||
|
@ -199,7 +197,7 @@ describe('rule status panel', () => {
|
|||
await nextTick();
|
||||
});
|
||||
|
||||
expect(bulkEnableRules).toHaveBeenCalledTimes(1);
|
||||
expect(mockAPIs.bulkEnableRules).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if rule is already enabled should do nothing when picking enable in the dropdown', async () => {
|
||||
|
|
|
@ -126,12 +126,8 @@ export const RuleStatusPanel: React.FC<RuleStatusPanelWithApiProps> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<RuleStatusDropdown
|
||||
disableRule={async () => {
|
||||
await bulkDisableRules({ ids: [rule.id] });
|
||||
}}
|
||||
enableRule={async () => {
|
||||
await bulkEnableRules({ ids: [rule.id] });
|
||||
}}
|
||||
disableRule={() => bulkDisableRules({ ids: [rule.id] })}
|
||||
enableRule={() => bulkEnableRules({ ids: [rule.id] })}
|
||||
snoozeRule={async () => {}}
|
||||
unsnoozeRule={async () => {}}
|
||||
rule={rule}
|
||||
|
|
|
@ -10,6 +10,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiTitle, EuiFlyoutHeader, EuiFlyout, EuiFlyoutBody, EuiPortal } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
Rule,
|
||||
RuleTypeParams,
|
||||
|
@ -38,6 +40,14 @@ import { getRuleWithInvalidatedFields } from '../../lib/value_validators';
|
|||
import { DEFAULT_RULE_INTERVAL } from '../../constants';
|
||||
import { triggersActionsUiConfig } from '../../../common/lib/config_api';
|
||||
import { getInitialInterval } from './get_initial_interval';
|
||||
import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content';
|
||||
|
||||
const defaultCreateRuleErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleAdd.saveErrorNotificationText',
|
||||
{
|
||||
defaultMessage: 'Cannot create rule.',
|
||||
}
|
||||
);
|
||||
|
||||
const RuleAdd = ({
|
||||
consumer,
|
||||
|
@ -238,12 +248,17 @@ const RuleAdd = ({
|
|||
);
|
||||
return newRule;
|
||||
} catch (errorRes) {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
i18n.translate('xpack.triggersActionsUI.sections.ruleAdd.saveErrorNotificationText', {
|
||||
defaultMessage: 'Cannot create rule.',
|
||||
})
|
||||
const message = parseRuleCircuitBreakerErrorMessage(
|
||||
errorRes.body?.message || defaultCreateRuleErrorMessage
|
||||
);
|
||||
toasts.addDanger({
|
||||
title: message.summary,
|
||||
...(message.details && {
|
||||
text: toMountPoint(
|
||||
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
|
||||
),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -219,9 +219,9 @@ describe('rule_edit', () => {
|
|||
await act(async () => {
|
||||
wrapper.find('[data-test-subj="saveEditedRuleButton"]').last().simulate('click');
|
||||
});
|
||||
expect(useKibanaMock().services.notifications.toasts.addDanger).toHaveBeenCalledWith(
|
||||
'Fail message'
|
||||
);
|
||||
expect(useKibanaMock().services.notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
title: 'Fail message',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass in the config into `getRuleErrors`', async () => {
|
||||
|
|
|
@ -26,6 +26,8 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
Rule,
|
||||
RuleFlyoutCloseReason,
|
||||
|
@ -47,6 +49,14 @@ import { ConfirmRuleClose } from './confirm_rule_close';
|
|||
import { hasRuleChanged } from './has_rule_changed';
|
||||
import { getRuleWithInvalidatedFields } from '../../lib/value_validators';
|
||||
import { triggersActionsUiConfig } from '../../../common/lib/config_api';
|
||||
import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content';
|
||||
|
||||
const defaultUpdateRuleErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.ruleEdit.saveErrorNotificationText',
|
||||
{
|
||||
defaultMessage: 'Cannot update rule.',
|
||||
}
|
||||
);
|
||||
|
||||
const cloneAndMigrateRule = (initialRule: Rule) => {
|
||||
const clonedRule = cloneDeep(omit(initialRule, 'notifyWhen', 'throttle'));
|
||||
|
@ -181,12 +191,17 @@ export const RuleEdit = ({
|
|||
);
|
||||
}
|
||||
} catch (errorRes) {
|
||||
toasts.addDanger(
|
||||
errorRes.body?.message ??
|
||||
i18n.translate('xpack.triggersActionsUI.sections.ruleEdit.saveErrorNotificationText', {
|
||||
defaultMessage: 'Cannot update rule.',
|
||||
})
|
||||
const message = parseRuleCircuitBreakerErrorMessage(
|
||||
errorRes.body?.message || defaultUpdateRuleErrorMessage
|
||||
);
|
||||
toasts.addDanger({
|
||||
title: message.summary,
|
||||
...(message.details && {
|
||||
text: toMountPoint(
|
||||
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
|
||||
),
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,19 @@ import { RuleStatusDropdown, ComponentOpts } from './rule_status_dropdown';
|
|||
const NOW_STRING = '2020-03-01T00:00:00.000Z';
|
||||
const SNOOZE_UNTIL = new Date('2020-03-04T00:00:00.000Z');
|
||||
|
||||
jest.mock('../../../../common/lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
notifications: {
|
||||
toasts: {
|
||||
addSuccess: jest.fn(),
|
||||
addDanger: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RuleStatusDropdown', () => {
|
||||
const enableRule = jest.fn();
|
||||
const disableRule = jest.fn();
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { RuleSnooze } from '@kbn/alerting-plugin/common';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
EuiLoadingSpinner,
|
||||
EuiPopover,
|
||||
|
@ -20,9 +22,11 @@ import {
|
|||
EuiText,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { SnoozePanel } from './rule_snooze';
|
||||
import { isRuleSnoozed } from '../../../lib';
|
||||
import { Rule, SnoozeSchedule } from '../../../../types';
|
||||
import { Rule, SnoozeSchedule, BulkOperationResponse } from '../../../../types';
|
||||
import { ToastWithCircuitBreakerContent } from '../../../components/toast_with_circuit_breaker_content';
|
||||
|
||||
export type SnoozeUnit = 'm' | 'h' | 'd' | 'w' | 'M';
|
||||
const SNOOZE_END_TIME_FORMAT = 'LL @ LT';
|
||||
|
@ -35,8 +39,8 @@ type DropdownRuleRecord = Pick<
|
|||
export interface ComponentOpts {
|
||||
rule: DropdownRuleRecord;
|
||||
onRuleChanged: () => void;
|
||||
enableRule: () => Promise<void>;
|
||||
disableRule: () => Promise<void>;
|
||||
enableRule: () => Promise<BulkOperationResponse>;
|
||||
disableRule: () => Promise<BulkOperationResponse>;
|
||||
snoozeRule: (snoozeSchedule: SnoozeSchedule) => Promise<void>;
|
||||
unsnoozeRule: (scheduleIds?: string[]) => Promise<void>;
|
||||
isEditable: boolean;
|
||||
|
@ -58,6 +62,10 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
|
|||
const [isEnabled, setIsEnabled] = useState<boolean>(rule.enabled);
|
||||
const [isSnoozed, setIsSnoozed] = useState<boolean>(!hideSnoozeOption && isRuleSnoozed(rule));
|
||||
|
||||
const {
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
useEffect(() => {
|
||||
setIsEnabled(rule.enabled);
|
||||
}, [rule.enabled]);
|
||||
|
@ -70,6 +78,25 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
|
|||
const onClickBadge = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), [setIsPopoverOpen]);
|
||||
const onClosePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]);
|
||||
|
||||
const enableRuleInternal = useCallback(async () => {
|
||||
const { errors } = await enableRule();
|
||||
|
||||
if (!errors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = parseRuleCircuitBreakerErrorMessage(errors[0].message);
|
||||
toasts.addDanger({
|
||||
title: message.summary,
|
||||
...(message.details && {
|
||||
text: toMountPoint(
|
||||
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
|
||||
),
|
||||
}),
|
||||
});
|
||||
throw new Error();
|
||||
}, [enableRule, toasts]);
|
||||
|
||||
const onChangeEnabledStatus = useCallback(
|
||||
async (enable: boolean) => {
|
||||
if (rule.enabled === enable) {
|
||||
|
@ -78,7 +105,7 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
|
|||
setIsUpdating(true);
|
||||
try {
|
||||
if (enable) {
|
||||
await enableRule();
|
||||
await enableRuleInternal();
|
||||
} else {
|
||||
await disableRule();
|
||||
}
|
||||
|
@ -88,7 +115,7 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
|
|||
setIsUpdating(false);
|
||||
}
|
||||
},
|
||||
[rule.enabled, isEnabled, onRuleChanged, enableRule, disableRule]
|
||||
[rule.enabled, isEnabled, onRuleChanged, enableRuleInternal, disableRule]
|
||||
);
|
||||
|
||||
const onSnoozeRule = useCallback(
|
||||
|
|
|
@ -11,6 +11,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { capitalize, isEmpty, isEqual, sortBy } from 'lodash';
|
||||
import { KueryNode } from '@kbn/es-query';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
|
||||
import React, {
|
||||
lazy,
|
||||
useEffect,
|
||||
|
@ -90,6 +92,7 @@ import { useLoadRuleAggregationsQuery } from '../../../hooks/use_load_rule_aggre
|
|||
import { useLoadRuleTypesQuery } from '../../../hooks/use_load_rule_types_query';
|
||||
import { useLoadRulesQuery } from '../../../hooks/use_load_rules_query';
|
||||
import { useLoadConfigQuery } from '../../../hooks/use_load_config_query';
|
||||
import { ToastWithCircuitBreakerContent } from '../../../components/toast_with_circuit_breaker_content';
|
||||
|
||||
import {
|
||||
getConfirmDeletionButtonText,
|
||||
|
@ -550,15 +553,15 @@ export const RulesList = ({
|
|||
};
|
||||
|
||||
const onDisableRule = useCallback(
|
||||
async (rule: RuleTableItem) => {
|
||||
await bulkDisableRules({ http, ids: [rule.id] });
|
||||
(rule: RuleTableItem) => {
|
||||
return bulkDisableRules({ http, ids: [rule.id] });
|
||||
},
|
||||
[bulkDisableRules]
|
||||
);
|
||||
|
||||
const onEnableRule = useCallback(
|
||||
async (rule: RuleTableItem) => {
|
||||
await bulkEnableRules({ http, ids: [rule.id] });
|
||||
(rule: RuleTableItem) => {
|
||||
return bulkEnableRules({ http, ids: [rule.id] });
|
||||
},
|
||||
[bulkEnableRules]
|
||||
);
|
||||
|
@ -675,7 +678,23 @@ export const RulesList = ({
|
|||
: await bulkEnableRules({ http, ids: selectedIds });
|
||||
|
||||
setIsEnablingRules(false);
|
||||
showToast({ action: 'ENABLE', errors, total });
|
||||
|
||||
const circuitBreakerError = errors.find(
|
||||
(error) => !!parseRuleCircuitBreakerErrorMessage(error.message).details
|
||||
);
|
||||
|
||||
if (circuitBreakerError) {
|
||||
const parsedError = parseRuleCircuitBreakerErrorMessage(circuitBreakerError.message);
|
||||
toasts.addDanger({
|
||||
title: parsedError.summary,
|
||||
text: toMountPoint(
|
||||
<ToastWithCircuitBreakerContent>{parsedError.details}</ToastWithCircuitBreakerContent>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
showToast({ action: 'ENABLE', errors, total });
|
||||
}
|
||||
|
||||
await refreshRules();
|
||||
onClearSelection();
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
TriggersActionsUiConfig,
|
||||
RuleTypeRegistryContract,
|
||||
SnoozeSchedule,
|
||||
BulkOperationResponse,
|
||||
} from '../../../../types';
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../constants';
|
||||
import { shouldShowDurationWarning } from '../../../lib/execution_duration_utils';
|
||||
|
@ -125,8 +126,8 @@ export interface RulesListTableProps {
|
|||
onTagClose?: (rule: RuleTableItem) => void;
|
||||
onPercentileOptionsChange?: (options: EuiSelectableOption[]) => void;
|
||||
onRuleChanged: () => Promise<void>;
|
||||
onEnableRule: (rule: RuleTableItem) => Promise<void>;
|
||||
onDisableRule: (rule: RuleTableItem) => Promise<void>;
|
||||
onEnableRule: (rule: RuleTableItem) => Promise<BulkOperationResponse>;
|
||||
onDisableRule: (rule: RuleTableItem) => Promise<BulkOperationResponse>;
|
||||
onSnoozeRule: (rule: RuleTableItem, snoozeSchedule: SnoozeSchedule) => Promise<void>;
|
||||
onUnsnoozeRule: (rule: RuleTableItem, scheduleIds?: string[]) => Promise<void>;
|
||||
onSelectAll: () => void;
|
||||
|
@ -193,8 +194,8 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
onManageLicenseClick = EMPTY_HANDLER,
|
||||
onPercentileOptionsChange = EMPTY_HANDLER,
|
||||
onRuleChanged,
|
||||
onEnableRule = EMPTY_HANDLER,
|
||||
onDisableRule = EMPTY_HANDLER,
|
||||
onEnableRule,
|
||||
onDisableRule,
|
||||
onSnoozeRule = EMPTY_HANDLER,
|
||||
onUnsnoozeRule = EMPTY_HANDLER,
|
||||
onSelectAll = EMPTY_HANDLER,
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function bulkEditWithCircuitBreakerTests({ getService }: FtrProvi
|
|||
|
||||
expect(body.errors.length).eql(2);
|
||||
expect(body.errors[0].message).eql(
|
||||
'Failed to bulk edit rule - Run limit reached: The rule has 12 runs per minute; there are only 1 runs per minute available.'
|
||||
'Error validating circuit breaker - Rules cannot be bulk edited. The maximum number of runs per minute would be exceeded. - The rules have 12 runs per minute; there is only 1 run per minute available. Before you can modify these rules, you must disable other rules or change their check intervals so they run less frequently.'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ export default function bulkEnableWithCircuitBreakerTests({ getService }: FtrPro
|
|||
|
||||
expect(body.errors.length).eql(2);
|
||||
expect(body.errors[0].message).eql(
|
||||
'Error validating enable rule data - Run limit reached: The rule has 9 runs per minute; there are only 4 runs per minute available.'
|
||||
'Error validating circuit breaker - Rules cannot be bulk enabled. The maximum number of runs per minute would be exceeded. - The rules have 9 runs per minute; there are only 4 runs per minute available. Before you can modify these rules, you must disable other rules or change their check intervals so they run less frequently.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
|
||||
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../../common/lib';
|
||||
|
||||
|
@ -26,11 +27,17 @@ export default function createWithCircuitBreakerTests({ getService }: FtrProvide
|
|||
.expect(200);
|
||||
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
const {
|
||||
body: { message },
|
||||
} = await supertest
|
||||
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ schedule: { interval: '10s' } }))
|
||||
.expect(400);
|
||||
|
||||
expect(message).eql(
|
||||
`Error validating circuit breaker - Rule 'abc' cannot be created. The maximum number of runs per minute would be exceeded. - The rule has 6 runs per minute; there are only 4 runs per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should prevent rules from being created across spaces', async () => {
|
||||
|
@ -41,11 +48,17 @@ export default function createWithCircuitBreakerTests({ getService }: FtrProvide
|
|||
.expect(200);
|
||||
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
const {
|
||||
body: { message },
|
||||
} = await supertest
|
||||
.post(`${getUrlPrefix('space2')}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ schedule: { interval: '10s' } }))
|
||||
.expect(400);
|
||||
|
||||
expect(message).eql(
|
||||
`Error validating circuit breaker - Rule 'abc' cannot be created. The maximum number of runs per minute would be exceeded. - The rule has 6 runs per minute; there are only 4 runs per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow disabled rules to go over the circuit breaker', async () => {
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function enableWithCircuitBreakerTests({ getService }: FtrProvide
|
|||
.expect(400);
|
||||
|
||||
expect(body.message).eql(
|
||||
'Error validating enable rule data - Run limit reached: The rule has 12 runs per minute; there are only 4 runs per minute available.'
|
||||
`Error validating circuit breaker - Rule 'abc' cannot be enabled. The maximum number of runs per minute would be exceeded. - The rule has 12 runs per minute; there are only 4 runs per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ export default function updateWithCircuitBreakerTests({ getService }: FtrProvide
|
|||
.expect(400);
|
||||
|
||||
expect(body.message).eql(
|
||||
'Error validating update data - Run limit reached: The rule has 12 runs per minute; there are only 7 runs per minute available.'
|
||||
`Error validating circuit breaker - Rule 'abc' cannot be updated. The maximum number of runs per minute would be exceeded. - The rule has 12 runs per minute; there are only 4 runs per minute available. Before you can modify this rule, you must increase its check interval so that it runs less frequently. Alternatively, disable other rules or change their check intervals.`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue