mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[RAM] Add _snooze API (#127081)
* [RAM] Add _snooze API * Switch empty snoozeEndTime to -1 * Convert API to internal * Ensure snoozeEndTime is in the future * Update x-pack/plugins/alerting/server/routes/snooze_rule.ts Co-authored-by: Gidi Meir Morris <github@gidi.io> * Add integration tests for snooze API * Fix tests Co-authored-by: Gidi Meir Morris <github@gidi.io>
This commit is contained in:
parent
5dc8e4b638
commit
a467450a47
25 changed files with 922 additions and 8 deletions
|
@ -94,6 +94,7 @@ export interface Alert<Params extends AlertTypeParams = never> {
|
|||
mutedInstanceIds: string[];
|
||||
executionStatus: AlertExecutionStatus;
|
||||
monitoring?: RuleMonitoring;
|
||||
snoozeEndTime?: Date | null; // Remove ? when this parameter is made available in the public API
|
||||
}
|
||||
|
||||
export type SanitizedAlert<Params extends AlertTypeParams = never> = Omit<Alert<Params>, 'apiKey'>;
|
||||
|
|
|
@ -44,6 +44,7 @@ export enum WriteOperations {
|
|||
UnmuteAll = 'unmuteAll',
|
||||
MuteAlert = 'muteAlert',
|
||||
UnmuteAlert = 'unmuteAlert',
|
||||
Snooze = 'snooze',
|
||||
}
|
||||
|
||||
export interface EnsureAuthorizedOpts {
|
||||
|
|
|
@ -18,3 +18,4 @@ export type { ErrorThatHandlesItsOwnResponse, ElasticsearchError };
|
|||
export { getEsErrorMessage };
|
||||
export type { AlertTypeDisabledReason } from './alert_type_disabled';
|
||||
export { AlertTypeDisabledError } from './alert_type_disabled';
|
||||
export { RuleMutedError } from './rule_muted';
|
||||
|
|
19
x-pack/plugins/alerting/server/lib/errors/rule_muted.ts
Normal file
19
x-pack/plugins/alerting/server/lib/errors/rule_muted.ts
Normal file
|
@ -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 { KibanaResponseFactory } from '../../../../../../src/core/server';
|
||||
import { ErrorThatHandlesItsOwnResponse } from './types';
|
||||
|
||||
export class RuleMutedError extends Error implements ErrorThatHandlesItsOwnResponse {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public sendResponse(res: KibanaResponseFactory) {
|
||||
return res.badRequest({ body: { message: this.message } });
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ export type {
|
|||
ErrorThatHandlesItsOwnResponse,
|
||||
ElasticsearchError,
|
||||
} from './errors';
|
||||
export { AlertTypeDisabledError, isErrorThatHandlesItsOwnResponse } from './errors';
|
||||
export { AlertTypeDisabledError, RuleMutedError, isErrorThatHandlesItsOwnResponse } from './errors';
|
||||
export {
|
||||
executionStatusFromState,
|
||||
executionStatusFromError,
|
||||
|
|
13
x-pack/plugins/alerting/server/lib/validate_snooze_date.ts
Normal file
13
x-pack/plugins/alerting/server/lib/validate_snooze_date.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const validateSnoozeDate = (date: string) => {
|
||||
const parsedValue = Date.parse(date);
|
||||
if (isNaN(parsedValue)) return `Invalid date: ${date}`;
|
||||
if (parsedValue <= Date.now()) return `Invalid snooze date as it is in the past: ${date}`;
|
||||
return;
|
||||
};
|
|
@ -84,6 +84,7 @@ const rewriteBodyRes: RewriteResponseCase<FindResult<AlertTypeParams>> = ({
|
|||
executionStatus,
|
||||
actions,
|
||||
scheduledTaskId,
|
||||
snoozeEndTime,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
|
@ -97,6 +98,8 @@ const rewriteBodyRes: RewriteResponseCase<FindResult<AlertTypeParams>> = ({
|
|||
mute_all: muteAll,
|
||||
muted_alert_ids: mutedInstanceIds,
|
||||
scheduled_task_id: scheduledTaskId,
|
||||
// Remove this object spread boolean check after snoozeEndTime is added to the public API
|
||||
...(snoozeEndTime !== undefined ? { snooze_end_time: snoozeEndTime } : {}),
|
||||
execution_status: executionStatus && {
|
||||
...omit(executionStatus, 'lastExecutionDate', 'lastDuration'),
|
||||
last_execution_date: executionStatus.lastExecutionDate,
|
||||
|
|
|
@ -35,6 +35,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedAlert<AlertTypeParams>> = ({
|
|||
executionStatus,
|
||||
actions,
|
||||
scheduledTaskId,
|
||||
snoozeEndTime,
|
||||
...rest
|
||||
}) => ({
|
||||
...rest,
|
||||
|
@ -47,6 +48,8 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedAlert<AlertTypeParams>> = ({
|
|||
notify_when: notifyWhen,
|
||||
mute_all: muteAll,
|
||||
muted_alert_ids: mutedInstanceIds,
|
||||
// Remove this object spread boolean check after snoozeEndTime is added to the public API
|
||||
...(snoozeEndTime !== undefined ? { snooze_end_time: snoozeEndTime } : {}),
|
||||
scheduled_task_id: scheduledTaskId,
|
||||
execution_status: executionStatus && {
|
||||
...omit(executionStatus, 'lastExecutionDate', 'lastDuration'),
|
||||
|
|
|
@ -29,6 +29,7 @@ import { muteAlertRoute } from './mute_alert';
|
|||
import { unmuteAllRuleRoute } from './unmute_all_rule';
|
||||
import { unmuteAlertRoute } from './unmute_alert';
|
||||
import { updateRuleApiKeyRoute } from './update_rule_api_key';
|
||||
import { snoozeRuleRoute } from './snooze_rule';
|
||||
|
||||
export interface RouteOptions {
|
||||
router: IRouter<AlertingRequestHandlerContext>;
|
||||
|
@ -61,4 +62,5 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
unmuteAllRuleRoute(router, licenseState);
|
||||
unmuteAlertRoute(router, licenseState);
|
||||
updateRuleApiKeyRoute(router, licenseState);
|
||||
snoozeRuleRoute(router, licenseState);
|
||||
}
|
||||
|
|
134
x-pack/plugins/alerting/server/routes/snooze_rule.test.ts
Normal file
134
x-pack/plugins/alerting/server/routes/snooze_rule.test.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 { snoozeRuleRoute } from './snooze_rule';
|
||||
import { httpServiceMock } from 'src/core/server/mocks';
|
||||
import { licenseStateMock } from '../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from './_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../rules_client.mock';
|
||||
import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../lib/license_api_access.ts', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const SNOOZE_END_TIME = '2025-03-07T00:00:00.000Z';
|
||||
|
||||
describe('snoozeAlertRoute', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date(2020, 3, 1));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
it('snoozes an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
snoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_snooze"`);
|
||||
|
||||
rulesClient.snooze.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
snooze_end_time: SNOOZE_END_TIME,
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.snooze).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.snooze.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"snoozeEndTime": "${SNOOZE_END_TIME}",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('also snoozes an alert when passed snoozeEndTime of -1', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
snoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_snooze"`);
|
||||
|
||||
rulesClient.snooze.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
snooze_end_time: -1,
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.snooze).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.snooze.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"snoozeEndTime": -1,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ensures the rule type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
snoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.snooze.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
});
|
62
x-pack/plugins/alerting/server/routes/snooze_rule.ts
Normal file
62
x-pack/plugins/alerting/server/routes/snooze_rule.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { IRouter } from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState, RuleMutedError } from '../lib';
|
||||
import { verifyAccessAndContext, RewriteRequestCase } from './lib';
|
||||
import { SnoozeOptions } from '../rules_client';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
|
||||
import { validateSnoozeDate } from '../lib/validate_snooze_date';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
const bodySchema = schema.object({
|
||||
snooze_end_time: schema.oneOf([
|
||||
schema.string({
|
||||
validate: validateSnoozeDate,
|
||||
}),
|
||||
schema.literal(-1),
|
||||
]),
|
||||
});
|
||||
|
||||
const rewriteBodyReq: RewriteRequestCase<SnoozeOptions> = ({ snooze_end_time: snoozeEndTime }) => ({
|
||||
snoozeEndTime,
|
||||
});
|
||||
|
||||
export const snoozeRuleRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_snooze`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
body: bodySchema,
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = context.alerting.getRulesClient();
|
||||
const params = req.params;
|
||||
const body = rewriteBodyReq(req.body);
|
||||
try {
|
||||
await rulesClient.snooze({ ...params, ...body });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleMutedError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -31,6 +31,7 @@ const createRulesClientMock = () => {
|
|||
listAlertTypes: jest.fn(),
|
||||
getAlertSummary: jest.fn(),
|
||||
getSpaceId: jest.fn(),
|
||||
snooze: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ export enum RuleAuditAction {
|
|||
MUTE_ALERT = 'rule_alert_mute',
|
||||
UNMUTE_ALERT = 'rule_alert_unmute',
|
||||
AGGREGATE = 'rule_aggregate',
|
||||
SNOOZE = 'rule_snooze',
|
||||
}
|
||||
|
||||
type VerbsTuple = [string, string, string];
|
||||
|
@ -42,6 +43,7 @@ const eventVerbs: Record<RuleAuditAction, VerbsTuple> = {
|
|||
rule_alert_mute: ['mute alert of', 'muting alert of', 'muted alert of'],
|
||||
rule_alert_unmute: ['unmute alert of', 'unmuting alert of', 'unmuted alert of'],
|
||||
rule_aggregate: ['access', 'accessing', 'accessed'],
|
||||
rule_snooze: ['snooze', 'snoozing', 'snoozed'],
|
||||
};
|
||||
|
||||
const eventTypes: Record<RuleAuditAction, EcsEventType> = {
|
||||
|
@ -59,6 +61,7 @@ const eventTypes: Record<RuleAuditAction, EcsEventType> = {
|
|||
rule_alert_mute: 'change',
|
||||
rule_alert_unmute: 'change',
|
||||
rule_aggregate: 'access',
|
||||
rule_snooze: 'change',
|
||||
};
|
||||
|
||||
export interface RuleAuditEventParams {
|
||||
|
|
|
@ -84,6 +84,8 @@ import {
|
|||
getModifiedSearch,
|
||||
modifyFilterKueryNode,
|
||||
} from './lib/mapped_params_utils';
|
||||
import { validateSnoozeDate } from '../lib/validate_snooze_date';
|
||||
import { RuleMutedError } from '../lib/errors/rule_muted';
|
||||
|
||||
export interface RegistryAlertTypeWithAuth extends RegistryRuleType {
|
||||
authorizedConsumers: string[];
|
||||
|
@ -144,6 +146,10 @@ export interface MuteOptions extends IndexType {
|
|||
alertInstanceId: string;
|
||||
}
|
||||
|
||||
export interface SnoozeOptions extends IndexType {
|
||||
snoozeEndTime: string | -1;
|
||||
}
|
||||
|
||||
export interface FindOptions extends IndexType {
|
||||
perPage?: number;
|
||||
page?: number;
|
||||
|
@ -202,6 +208,7 @@ export interface CreateOptions<Params extends RuleTypeParams> {
|
|||
| 'mutedInstanceIds'
|
||||
| 'actions'
|
||||
| 'executionStatus'
|
||||
| 'snoozeEndTime'
|
||||
> & { actions: NormalizedAlertAction[] };
|
||||
options?: {
|
||||
id?: string;
|
||||
|
@ -260,6 +267,7 @@ export class RulesClient {
|
|||
private readonly fieldsToExcludeFromPublicApi: Array<keyof SanitizedRule> = [
|
||||
'monitoring',
|
||||
'mapped_params',
|
||||
'snoozeEndTime',
|
||||
];
|
||||
|
||||
constructor({
|
||||
|
@ -372,6 +380,7 @@ export class RulesClient {
|
|||
updatedBy: username,
|
||||
createdAt: new Date(createTime).toISOString(),
|
||||
updatedAt: new Date(createTime).toISOString(),
|
||||
snoozeEndTime: null,
|
||||
params: updatedParams as RawRule['params'],
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
|
@ -1476,6 +1485,85 @@ export class RulesClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async snooze({
|
||||
id,
|
||||
snoozeEndTime,
|
||||
}: {
|
||||
id: string;
|
||||
snoozeEndTime: string | -1;
|
||||
}): Promise<void> {
|
||||
if (typeof snoozeEndTime === 'string') {
|
||||
const snoozeDateValidationMsg = validateSnoozeDate(snoozeEndTime);
|
||||
if (snoozeDateValidationMsg) {
|
||||
throw new RuleMutedError(snoozeDateValidationMsg);
|
||||
}
|
||||
}
|
||||
return await retryIfConflicts(
|
||||
this.logger,
|
||||
`rulesClient.snooze('${id}', ${snoozeEndTime})`,
|
||||
async () => await this.snoozeWithOCC({ id, snoozeEndTime })
|
||||
);
|
||||
}
|
||||
|
||||
private async snoozeWithOCC({ id, snoozeEndTime }: { id: string; snoozeEndTime: string | -1 }) {
|
||||
const { attributes, version } = await this.unsecuredSavedObjectsClient.get<RawRule>(
|
||||
'alert',
|
||||
id
|
||||
);
|
||||
|
||||
try {
|
||||
await this.authorization.ensureAuthorized({
|
||||
ruleTypeId: attributes.alertTypeId,
|
||||
consumer: attributes.consumer,
|
||||
operation: WriteOperations.MuteAll,
|
||||
entity: AlertingAuthorizationEntity.Rule,
|
||||
});
|
||||
|
||||
if (attributes.actions.length) {
|
||||
await this.actionsAuthorization.ensureAuthorized('execute');
|
||||
}
|
||||
} catch (error) {
|
||||
this.auditLogger?.log(
|
||||
ruleAuditEvent({
|
||||
action: RuleAuditAction.SNOOZE,
|
||||
savedObject: { type: 'alert', id },
|
||||
error,
|
||||
})
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.auditLogger?.log(
|
||||
ruleAuditEvent({
|
||||
action: RuleAuditAction.SNOOZE,
|
||||
outcome: 'unknown',
|
||||
savedObject: { type: 'alert', id },
|
||||
})
|
||||
);
|
||||
|
||||
this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId);
|
||||
|
||||
// If snoozeEndTime is -1, instead mute all
|
||||
const newAttrs =
|
||||
snoozeEndTime === -1
|
||||
? { muteAll: true, snoozeEndTime: null }
|
||||
: { snoozeEndTime: new Date(snoozeEndTime).toISOString(), muteAll: false };
|
||||
|
||||
const updateAttributes = this.updateMeta({
|
||||
...newAttrs,
|
||||
updatedBy: await this.getUserName(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
const updateOptions = { version };
|
||||
|
||||
await partiallyUpdateAlert(
|
||||
this.unsecuredSavedObjectsClient,
|
||||
id,
|
||||
updateAttributes,
|
||||
updateOptions
|
||||
);
|
||||
}
|
||||
|
||||
public async muteAll({ id }: { id: string }): Promise<void> {
|
||||
return await retryIfConflicts(
|
||||
this.logger,
|
||||
|
|
|
@ -294,6 +294,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
actions: [
|
||||
{
|
||||
|
@ -426,6 +427,7 @@ describe('create()', () => {
|
|||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"snoozeEndTime": null,
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
|
@ -496,6 +498,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
actions: [
|
||||
{
|
||||
|
@ -555,6 +558,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
actions: [
|
||||
{
|
||||
|
@ -627,6 +631,7 @@ describe('create()', () => {
|
|||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"snoozeEndTime": null,
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
|
@ -1034,6 +1039,7 @@ describe('create()', () => {
|
|||
monitoring: getDefaultRuleMonitoring(),
|
||||
meta: { versionApiKeyLastmodified: kibanaVersion },
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
name: 'abc',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
|
@ -1231,6 +1237,7 @@ describe('create()', () => {
|
|||
monitoring: getDefaultRuleMonitoring(),
|
||||
meta: { versionApiKeyLastmodified: kibanaVersion },
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
name: 'abc',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
|
@ -1397,6 +1404,7 @@ describe('create()', () => {
|
|||
monitoring: getDefaultRuleMonitoring(),
|
||||
meta: { versionApiKeyLastmodified: kibanaVersion },
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
name: 'abc',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
|
@ -1505,6 +1513,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
actions: [
|
||||
|
@ -1561,6 +1570,7 @@ describe('create()', () => {
|
|||
throttle: '10m',
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
|
@ -1634,6 +1644,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
actions: [
|
||||
|
@ -1690,6 +1701,7 @@ describe('create()', () => {
|
|||
throttle: '10m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
|
@ -1763,6 +1775,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onActiveAlert',
|
||||
actions: [
|
||||
|
@ -1819,6 +1832,7 @@ describe('create()', () => {
|
|||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
|
@ -1901,6 +1915,7 @@ describe('create()', () => {
|
|||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
actions: [
|
||||
{
|
||||
|
@ -1964,6 +1979,7 @@ describe('create()', () => {
|
|||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
|
@ -2332,6 +2348,7 @@ describe('create()', () => {
|
|||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
|
@ -2432,6 +2449,7 @@ describe('create()', () => {
|
|||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
muteAll: false,
|
||||
snoozeEndTime: null,
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
|
|
|
@ -30,6 +30,7 @@ export const AlertAttributesExcludedFromAAD = [
|
|||
'updatedAt',
|
||||
'executionStatus',
|
||||
'monitoring',
|
||||
'snoozeEndTime',
|
||||
];
|
||||
|
||||
// useful for Pick<RawAlert, AlertAttributesExcludedFromAADType> which is a
|
||||
|
@ -43,7 +44,8 @@ export type AlertAttributesExcludedFromAADType =
|
|||
| 'updatedBy'
|
||||
| 'updatedAt'
|
||||
| 'executionStatus'
|
||||
| 'monitoring';
|
||||
| 'monitoring'
|
||||
| 'snoozeEndTime';
|
||||
|
||||
export function setupSavedObjects(
|
||||
savedObjects: SavedObjectsServiceSetup,
|
||||
|
|
|
@ -244,7 +244,7 @@ export interface RawRule extends SavedObjectAttributes {
|
|||
meta?: AlertMeta;
|
||||
executionStatus: RawRuleExecutionStatus;
|
||||
monitoring?: RuleMonitoring;
|
||||
snoozeEndTime?: string;
|
||||
snoozeEndTime?: string | null; // Remove ? when this parameter is made available in the public API
|
||||
}
|
||||
|
||||
export type AlertInfoParams = Pick<
|
||||
|
|
|
@ -86,6 +86,17 @@ export class AlertUtils {
|
|||
return request;
|
||||
}
|
||||
|
||||
public getSnoozeRequest(alertId: string) {
|
||||
const request = this.supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(this.space.id)}/internal/alerting/rule/${alertId}/_snooze`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json');
|
||||
if (this.user) {
|
||||
return request.auth(this.user.username, this.user.password);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public getMuteAllRequest(alertId: string) {
|
||||
const request = this.supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_mute_all`)
|
||||
|
|
|
@ -82,7 +82,9 @@ const findTestUtils = (
|
|||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
execution_status: match.execution_status,
|
||||
...(describeType === 'internal' ? { monitoring: match.monitoring } : {}),
|
||||
...(describeType === 'internal'
|
||||
? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time }
|
||||
: {}),
|
||||
});
|
||||
expect(Date.parse(match.created_at)).to.be.greaterThan(0);
|
||||
expect(Date.parse(match.updated_at)).to.be.greaterThan(0);
|
||||
|
@ -281,7 +283,9 @@ const findTestUtils = (
|
|||
created_at: match.created_at,
|
||||
updated_at: match.updated_at,
|
||||
execution_status: match.execution_status,
|
||||
...(describeType === 'internal' ? { monitoring: match.monitoring } : {}),
|
||||
...(describeType === 'internal'
|
||||
? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time }
|
||||
: {}),
|
||||
});
|
||||
expect(Date.parse(match.created_at)).to.be.greaterThan(0);
|
||||
expect(Date.parse(match.updated_at)).to.be.greaterThan(0);
|
||||
|
|
|
@ -81,7 +81,12 @@ const getTestUtils = (
|
|||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
execution_status: response.body.execution_status,
|
||||
...(describeType === 'internal' ? { monitoring: response.body.monitoring } : {}),
|
||||
...(describeType === 'internal'
|
||||
? {
|
||||
monitoring: response.body.monitoring,
|
||||
snooze_end_time: response.body.snooze_end_time,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
expect(Date.parse(response.body.created_at)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0);
|
||||
|
|
|
@ -53,6 +53,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
|
|||
loadTestFile(require.resolve('./mustache_templates'));
|
||||
loadTestFile(require.resolve('./health'));
|
||||
loadTestFile(require.resolve('./excluded'));
|
||||
loadTestFile(require.resolve('./snooze'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { UserAtSpaceScenarios } from '../../scenarios';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import {
|
||||
AlertUtils,
|
||||
checkAAD,
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
getConsumerUnauthorizedErrorMessage,
|
||||
getProducerUnauthorizedErrorMessage,
|
||||
} from '../../../common/lib';
|
||||
|
||||
const FUTURE_SNOOZE_TIME = '9999-12-31T06:00:00.000Z';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createSnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('snooze', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
||||
after(() => objectRemover.removeAll());
|
||||
|
||||
for (const scenario of UserAtSpaceScenarios) {
|
||||
const { user, space } = scenario;
|
||||
const alertUtils = new AlertUtils({ user, space, supertestWithoutAuth });
|
||||
|
||||
describe(scenario.id, () => {
|
||||
it('should handle snooze rule request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getConsumerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.noop',
|
||||
'alertsFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: `Unauthorized to execute actions`,
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle snooze rule request appropriately when consumer is the same as producer', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
rule_type_id: 'test.restricted-noop',
|
||||
consumer: 'alertsRestrictedFixture',
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'global_read at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getConsumerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.restricted-noop',
|
||||
'alertsRestrictedFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle snooze rule request appropriately when consumer is not the producer', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
rule_type_id: 'test.unrestricted-noop',
|
||||
consumer: 'alertsFixture',
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getConsumerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.unrestricted-noop',
|
||||
'alertsFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getProducerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.unrestricted-noop',
|
||||
'alertsRestrictedFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle snooze rule request appropriately when consumer is "alerts"', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
rule_type_id: 'test.restricted-noop',
|
||||
consumer: 'alerts',
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getConsumerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.restricted-noop',
|
||||
'alerts'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getProducerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.restricted-noop',
|
||||
'alertsRestrictedFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle snooze rule request appropriately when snoozeEndTime is -1', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: -1 });
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getConsumerUnauthorizedErrorMessage(
|
||||
'muteAll',
|
||||
'test.noop',
|
||||
'alertsFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: `Unauthorized to execute actions`,
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(null);
|
||||
expect(updatedAlert.mute_all).to.eql(true);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -72,7 +72,9 @@ const findTestUtils = (
|
|||
created_at: match.created_at,
|
||||
updated_at: match.updated_at,
|
||||
execution_status: match.execution_status,
|
||||
...(describeType === 'internal' ? { monitoring: match.monitoring } : {}),
|
||||
...(describeType === 'internal'
|
||||
? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time }
|
||||
: {}),
|
||||
});
|
||||
expect(Date.parse(match.created_at)).to.be.greaterThan(0);
|
||||
expect(Date.parse(match.updated_at)).to.be.greaterThan(0);
|
||||
|
|
|
@ -54,7 +54,9 @@ const getTestUtils = (
|
|||
created_at: response.body.created_at,
|
||||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
...(describeType === 'internal' ? { monitoring: response.body.monitoring } : {}),
|
||||
...(describeType === 'internal'
|
||||
? { monitoring: response.body.monitoring, snooze_end_time: response.body.snooze_end_time }
|
||||
: {}),
|
||||
});
|
||||
expect(Date.parse(response.body.created_at)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0);
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { Spaces } from '../../scenarios';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import {
|
||||
AlertUtils,
|
||||
checkAAD,
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
} from '../../../common/lib';
|
||||
|
||||
const FUTURE_SNOOZE_TIME = '9999-12-31T06:00:00.000Z';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createSnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('snooze', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
||||
after(() => objectRemover.removeAll());
|
||||
|
||||
const alertUtils = new AlertUtils({ space: Spaces.space1, supertestWithoutAuth });
|
||||
|
||||
it('should handle snooze rule request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });
|
||||
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle snooze rule request appropriately when snoozeEndTime is -1', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils
|
||||
.getSnoozeRequest(createdAlert.id)
|
||||
.send({ snooze_end_time: -1 });
|
||||
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.snooze_end_time).to.eql(null);
|
||||
expect(updatedAlert.mute_all).to.eql(true);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: 'alert',
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue