mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ResponseOps][Rules] Allow users to delete snooze schedule from a rule (#213247)
## Summary Resolves https://github.com/elastic/kibana/issues/198783 This PR allows to delete existing snooze schedule from a rule using schedule id. ### Checklist - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 ### How to test - Create a rule in kibana - Snooze that rule via new public API - delete that snooze schedule via public api Method: `DELETE` Path: `https://localhost:5601/api/alerting/rule/<ruleId>/snooze_schedule/<scheduleId>` ### Flaky test runner: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8049 ### Release note Allow users to delete a snooze schedule from a rule using schedule id --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: lcawl <lcawley@elastic.co>
This commit is contained in:
parent
fabb1c9ffd
commit
6088eb221e
28 changed files with 1179 additions and 35 deletions
|
@ -5261,6 +5261,59 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/alerting/rule/{ruleId}/snooze_schedule/{scheduleId}": {
|
||||
"delete": {
|
||||
"operationId": "delete-alerting-rule-ruleid-snooze-schedule-scheduleid",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The identifier for the rule.",
|
||||
"in": "path",
|
||||
"name": "ruleId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The identifier for the snooze schedule.",
|
||||
"in": "path",
|
||||
"name": "scheduleId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Indicates a successful call."
|
||||
},
|
||||
"400": {
|
||||
"description": "Indicates an invalid schema."
|
||||
},
|
||||
"403": {
|
||||
"description": "Indicates that this call is forbidden."
|
||||
},
|
||||
"404": {
|
||||
"description": "Indicates a rule with the given id does not exist."
|
||||
}
|
||||
},
|
||||
"summary": "Delete a snooze schedule for a rule",
|
||||
"tags": [
|
||||
"alerting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/alerting/rule/{rule_id}/alert/{alert_id}/_mute": {
|
||||
"post": {
|
||||
"operationId": "post-alerting-rule-rule-id-alert-alert-id-mute",
|
||||
|
|
|
@ -5261,6 +5261,59 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/alerting/rule/{ruleId}/snooze_schedule/{scheduleId}": {
|
||||
"delete": {
|
||||
"operationId": "delete-alerting-rule-ruleid-snooze-schedule-scheduleid",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The identifier for the rule.",
|
||||
"in": "path",
|
||||
"name": "ruleId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The identifier for the snooze schedule.",
|
||||
"in": "path",
|
||||
"name": "scheduleId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Indicates a successful call."
|
||||
},
|
||||
"400": {
|
||||
"description": "Indicates an invalid schema."
|
||||
},
|
||||
"403": {
|
||||
"description": "Indicates that this call is forbidden."
|
||||
},
|
||||
"404": {
|
||||
"description": "Indicates a rule with the given id does not exist."
|
||||
}
|
||||
},
|
||||
"summary": "Delete a snooze schedule for a rule",
|
||||
"tags": [
|
||||
"alerting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/alerting/rule/{rule_id}/alert/{alert_id}/_mute": {
|
||||
"post": {
|
||||
"operationId": "post-alerting-rule-rule-id-alert-alert-id-mute",
|
||||
|
|
|
@ -4173,6 +4173,42 @@ paths:
|
|||
tags:
|
||||
- alerting
|
||||
x-beta: true
|
||||
/api/alerting/rule/{ruleId}/snooze_schedule/{scheduleId}:
|
||||
delete:
|
||||
operationId: delete-alerting-rule-ruleid-snooze-schedule-scheduleid
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- description: The identifier for the rule.
|
||||
in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- description: The identifier for the snooze schedule.
|
||||
in: path
|
||||
name: scheduleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'400':
|
||||
description: Indicates an invalid schema.
|
||||
'403':
|
||||
description: Indicates that this call is forbidden.
|
||||
'404':
|
||||
description: Indicates a rule with the given id does not exist.
|
||||
summary: Delete a snooze schedule for a rule
|
||||
tags:
|
||||
- alerting
|
||||
x-beta: true
|
||||
/api/alerting/rules/_find:
|
||||
get:
|
||||
operationId: get-alerting-rules-find
|
||||
|
|
|
@ -4524,6 +4524,41 @@ paths:
|
|||
summary: Unmute an alert
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerting/rule/{ruleId}/snooze_schedule/{scheduleId}:
|
||||
delete:
|
||||
operationId: delete-alerting-rule-ruleid-snooze-schedule-scheduleid
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- description: The identifier for the rule.
|
||||
in: path
|
||||
name: ruleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- description: The identifier for the snooze schedule.
|
||||
in: path
|
||||
name: scheduleId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'400':
|
||||
description: Indicates an invalid schema.
|
||||
'403':
|
||||
description: Indicates that this call is forbidden.
|
||||
'404':
|
||||
description: Indicates a rule with the given id does not exist.
|
||||
summary: Delete a snooze schedule for a rule
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerting/rules/_find:
|
||||
get:
|
||||
operationId: get-alerting-rules-find
|
||||
|
|
15
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/unsnooze/external/schemas/v1.ts
vendored
Normal file
15
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/unsnooze/external/schemas/v1.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const unsnoozeParamsSchema = schema.object({
|
||||
ruleId: schema.string({ meta: { description: 'The identifier for the rule.' } }),
|
||||
scheduleId: schema.string({
|
||||
meta: { description: 'The identifier for the snooze schedule.' },
|
||||
}),
|
||||
});
|
10
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/unsnooze/external/types/v1.ts
vendored
Normal file
10
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/unsnooze/external/types/v1.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 type { TypeOf } from '@kbn/config-schema';
|
||||
import type { unsnoozeParamsSchemaV1 } from '../..';
|
||||
|
||||
export type UnsnoozeParams = TypeOf<typeof unsnoozeParamsSchemaV1>;
|
|
@ -5,9 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { unsnoozeParamsSchema, unsnoozeBodySchema } from './schemas/latest';
|
||||
export {
|
||||
unsnoozeParamsInternalSchema,
|
||||
unsnoozeBodyInternalSchema,
|
||||
} from './internal/schemas/latest';
|
||||
export { unsnoozeParamsSchema } from './external/schemas/latest';
|
||||
export type { UnsnoozeParams } from './external/types/latest';
|
||||
|
||||
export {
|
||||
unsnoozeParamsSchema as unsnoozeParamsSchemaV1,
|
||||
unsnoozeBodySchema as unsnoozeBodySchemaV1,
|
||||
} from './schemas/v1';
|
||||
unsnoozeParamsInternalSchema as unsnoozeParamsInternalSchemaV1,
|
||||
unsnoozeBodyInternalSchema as unsnoozeBodyInternalSchemaV1,
|
||||
} from './internal/schemas/v1';
|
||||
export { unsnoozeParamsSchema as unsnoozeParamsSchemaV1 } from './external/schemas/v1';
|
||||
export type { UnsnoozeParams as UnsnoozeParamsV1 } from './external/types/v1';
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './v1';
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
export const unsnoozeParamsSchema = schema.object({
|
||||
export const unsnoozeParamsInternalSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));
|
||||
|
||||
export const unsnoozeBodySchema = schema.object({
|
||||
export const unsnoozeBodyInternalSchema = schema.object({
|
||||
schedule_ids: scheduleIdsSchema,
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 type { RulesClientContext } from '../../../../rules_client';
|
||||
import { unsnoozeRule } from './unsnooze_rule';
|
||||
import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
|
||||
const loggerErrorMock = jest.fn();
|
||||
const getBulkMock = jest.fn();
|
||||
|
||||
const savedObjectsMock = savedObjectsRepositoryMock.create();
|
||||
savedObjectsMock.get = jest.fn().mockReturnValue({
|
||||
attributes: {
|
||||
actions: [],
|
||||
snoozeSchedule: [
|
||||
{
|
||||
duration: 600000,
|
||||
rRule: {
|
||||
interval: 1,
|
||||
freq: 3,
|
||||
dtstart: '2025-03-01T06:30:37.011Z',
|
||||
tzid: 'UTC',
|
||||
},
|
||||
id: 'snooze_schedule_1',
|
||||
},
|
||||
],
|
||||
},
|
||||
version: '9.0.0',
|
||||
});
|
||||
|
||||
const context = {
|
||||
logger: { error: loggerErrorMock },
|
||||
getActionsClient: () => {
|
||||
return {
|
||||
getBulk: getBulkMock,
|
||||
};
|
||||
},
|
||||
unsecuredSavedObjectsClient: savedObjectsMock,
|
||||
authorization: { ensureAuthorized: async () => {} },
|
||||
ruleTypeRegistry: {
|
||||
ensureRuleTypeEnabled: () => {},
|
||||
},
|
||||
getUserName: async () => {},
|
||||
} as unknown as RulesClientContext;
|
||||
|
||||
describe('validate unsnooze params', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should validate params correctly', async () => {
|
||||
await expect(
|
||||
unsnoozeRule(context, { id: '123', scheduleIds: ['snooze_schedule_1'] })
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should validate params with empty schedule ids correctly', async () => {
|
||||
await expect(unsnoozeRule(context, { id: '123', scheduleIds: [] })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw bad request for invalid params', async () => {
|
||||
// @ts-expect-error: testing invalid params
|
||||
await expect(unsnoozeRule(context, {})).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error validating unsnooze params - [id]: expected value of type [string] but got [undefined]"`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -38,7 +38,7 @@ import { unmuteAlertRoute } from './rule/apis/unmute_alert/unmute_alert_route';
|
|||
import { updateRuleApiKeyRoute } from './rule/apis/update_api_key/update_rule_api_key_route';
|
||||
import { bulkEditInternalRulesRoute } from './rule/apis/bulk_edit/bulk_edit_rules_route';
|
||||
import { snoozeRuleInternalRoute, snoozeRuleRoute } from './rule/apis/snooze';
|
||||
import { unsnoozeRuleRoute } from './rule/apis/unsnooze';
|
||||
import { unsnoozeRuleRoute, unsnoozeRuleInternalRoute } from './rule/apis/unsnooze';
|
||||
import { runSoonRoute } from './run_soon';
|
||||
import { bulkDeleteRulesRoute } from './rule/apis/bulk_delete/bulk_delete_rules_route';
|
||||
import { bulkEnableRulesRoute } from './rule/apis/bulk_enable/bulk_enable_rules_route';
|
||||
|
@ -124,6 +124,7 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
snoozeRuleInternalRoute(router, licenseState);
|
||||
snoozeRuleRoute(router, licenseState);
|
||||
unsnoozeRuleRoute(router, licenseState);
|
||||
unsnoozeRuleInternalRoute(router, licenseState);
|
||||
cloneRuleRoute(router, licenseState);
|
||||
getRuleTagsRoute(router, licenseState);
|
||||
registerRulesValueSuggestionsRoute(router, licenseState, config$!);
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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 { unsnoozeRuleRoute } from './unsnooze_rule_route';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../../../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../../../../lib/errors/rule_type_disabled';
|
||||
import type { SanitizedRule } from '../../../../../types';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../../../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('unsnoozeAlertRoute', () => {
|
||||
const mockedAlert: SanitizedRule<{
|
||||
bar: boolean;
|
||||
}> = {
|
||||
id: '1',
|
||||
alertTypeId: '1',
|
||||
schedule: { interval: '10s' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
createdAt: new Date('2020-08-20T19:23:38Z'),
|
||||
updatedAt: new Date('2020-08-20T19:23:38Z'),
|
||||
actions: [],
|
||||
snoozeSchedule: [
|
||||
{
|
||||
id: 'snooze_schedule_1',
|
||||
duration: 600000,
|
||||
rRule: {
|
||||
interval: 1,
|
||||
freq: 3,
|
||||
dtstart: '2025-03-01T06:30:37.011Z',
|
||||
tzid: 'UTC',
|
||||
},
|
||||
},
|
||||
],
|
||||
consumer: 'bar',
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
createdBy: '',
|
||||
updatedBy: '',
|
||||
apiKeyOwner: '',
|
||||
throttle: '30s',
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
it('unsnoozes a rule', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unsnoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.delete.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(
|
||||
`"/api/alerting/rule/{ruleId}/snooze_schedule/{scheduleId}"`
|
||||
);
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
rulesClient.unsnooze.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
ruleId: 'rule_1',
|
||||
scheduleId: 'snooze_schedule_1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.unsnooze).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.unsnooze.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "rule_1",
|
||||
"scheduleIds": Array [
|
||||
"snooze_schedule_1",
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ensures the rule type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
unsnoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
|
||||
rulesClient.unsnooze.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{ params: { ruleId: 'rule_1', scheduleId: 'snooze_schedule_1' }, body: {} },
|
||||
['ok', 'forbidden']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should throw error when snooze schedule is empty', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
rulesClient.get.mockResolvedValueOnce({ ...mockedAlert, snoozeSchedule: [] });
|
||||
|
||||
unsnoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{ params: { ruleId: 'rule_1', scheduleId: 'snooze_schedule_1' }, body: {} },
|
||||
['ok', 'forbidden']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Rule has no snooze schedules.]`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid snooze schedule id', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
unsnoozeRuleRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{ params: { ruleId: 'rule_1', scheduleId: 'random_schedule_1' }, body: {} },
|
||||
['ok', 'forbidden']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Rule has no snooze schedule with id random_schedule_1.]`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 Boom from '@hapi/boom';
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import {
|
||||
unsnoozeParamsSchema,
|
||||
type UnsnoozeParams,
|
||||
} from '../../../../../../common/routes/rule/apis/unsnooze';
|
||||
import type { ILicenseState } from '../../../../../lib';
|
||||
import { RuleMutedError } from '../../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../../types';
|
||||
import { BASE_ALERTING_API_PATH } from '../../../../../types';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../../constants';
|
||||
|
||||
export const unsnoozeRuleRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
||||
router.delete(
|
||||
{
|
||||
path: `${BASE_ALERTING_API_PATH}/rule/{ruleId}/snooze_schedule/{scheduleId}`,
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: 'Delete a snooze schedule for a rule',
|
||||
tags: ['oas-tag:alerting'],
|
||||
availability: {
|
||||
since: '8.19.0',
|
||||
stability: 'stable',
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
request: {
|
||||
params: unsnoozeParamsSchema,
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'Indicates a successful call.',
|
||||
},
|
||||
400: {
|
||||
description: 'Indicates an invalid schema.',
|
||||
},
|
||||
403: {
|
||||
description: 'Indicates that this call is forbidden.',
|
||||
},
|
||||
404: {
|
||||
description: 'Indicates a rule with the given id does not exist.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { ruleId, scheduleId }: UnsnoozeParams = req.params;
|
||||
try {
|
||||
const currentRule = await rulesClient.get({ id: ruleId });
|
||||
|
||||
if (!currentRule.snoozeSchedule?.length) {
|
||||
throw Boom.badRequest('Rule has no snooze schedules.');
|
||||
}
|
||||
|
||||
const scheduleToUnsnooze = currentRule.snoozeSchedule?.find(
|
||||
(schedule) => schedule.id === scheduleId
|
||||
);
|
||||
|
||||
if (!scheduleToUnsnooze) {
|
||||
throw Boom.notFound(`Rule has no snooze schedule with id ${scheduleId}.`);
|
||||
}
|
||||
|
||||
await rulesClient.unsnooze({ id: ruleId, scheduleIds: [scheduleId] });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleMutedError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -5,4 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { unsnoozeRuleRoute } from './unsnooze_rule_route';
|
||||
export { unsnoozeRuleRoute as unsnoozeRuleInternalRoute } from './internal/unsnooze_rule_route';
|
||||
export { unsnoozeRuleRoute } from './external/unsnooze_rule_route';
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './v1';
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { unsnoozeRuleRoute } from './unsnooze_rule_route';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled';
|
||||
import { licenseStateMock } from '../../../../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../../../../lib/errors/rule_type_disabled';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../../../lib/license_api_access', () => ({
|
||||
jest.mock('../../../../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
|
@ -8,18 +8,18 @@
|
|||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import {
|
||||
unsnoozeBodySchema,
|
||||
unsnoozeParamsSchema,
|
||||
} from '../../../../../common/routes/rule/apis/unsnooze';
|
||||
import type { ILicenseState } from '../../../../lib';
|
||||
import { RuleMutedError } from '../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types';
|
||||
unsnoozeBodyInternalSchema,
|
||||
unsnoozeParamsInternalSchema,
|
||||
} from '../../../../../../common/routes/rule/apis/unsnooze';
|
||||
import type { ILicenseState } from '../../../../../lib';
|
||||
import { RuleMutedError } from '../../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../../types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../../../types';
|
||||
import { transformUnsnoozeBodyV1 } from './transforms';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../constants';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../../constants';
|
||||
|
||||
export type UnsnoozeRuleRequestParamsV1 = TypeOf<typeof unsnoozeParamsSchema>;
|
||||
export type UnsnoozeRuleRequestParamsV1 = TypeOf<typeof unsnoozeParamsInternalSchema>;
|
||||
|
||||
export const unsnoozeRuleRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
|
@ -31,8 +31,8 @@ export const unsnoozeRuleRoute = (
|
|||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: { access: 'internal' },
|
||||
validate: {
|
||||
params: unsnoozeParamsSchema,
|
||||
body: unsnoozeBodySchema,
|
||||
params: unsnoozeParamsInternalSchema,
|
||||
body: unsnoozeBodyInternalSchema,
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
|
@ -153,7 +153,7 @@ export class AlertUtils {
|
|||
return request;
|
||||
}
|
||||
|
||||
public getUnsnoozeRequest(alertId: string) {
|
||||
public getUnsnoozeInternalRequest(alertId: string) {
|
||||
const request = this.supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(this.space.id)}/internal/alerting/rule/${alertId}/_unsnooze`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -167,6 +167,19 @@ export class AlertUtils {
|
|||
return request;
|
||||
}
|
||||
|
||||
public getUnsnoozeRequest(alertId: string, scheduleId: string) {
|
||||
const request = this.supertestWithoutAuth
|
||||
.delete(
|
||||
`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/snooze_schedule/${scheduleId}`
|
||||
)
|
||||
.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`)
|
||||
|
|
|
@ -28,6 +28,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
|
|||
loadTestFile(require.resolve('./snooze'));
|
||||
loadTestFile(require.resolve('./snooze_internal'));
|
||||
loadTestFile(require.resolve('./unsnooze'));
|
||||
loadTestFile(require.resolve('./unsnooze_internal'));
|
||||
loadTestFile(require.resolve('./global_execution_log'));
|
||||
loadTestFile(require.resolve('./get_global_execution_kpi'));
|
||||
loadTestFile(require.resolve('./get_action_error_log'));
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
export default function createUnsnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const NOW = new Date().toISOString();
|
||||
|
||||
describe('unsnooze', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
@ -62,11 +63,38 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id);
|
||||
const { body: snoozeSchedule } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(
|
||||
createdAlert.id,
|
||||
snoozeSchedule.schedule.id
|
||||
);
|
||||
|
||||
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: getUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
|
@ -122,14 +150,45 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id);
|
||||
const { body: snoozeSchedule } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(
|
||||
createdAlert.id,
|
||||
snoozeSchedule.schedule.id
|
||||
);
|
||||
|
||||
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: getUnauthorizedErrorMessage(
|
||||
'get',
|
||||
'test.restricted-noop',
|
||||
'alertsRestrictedFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
|
@ -179,11 +238,42 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id);
|
||||
const { body: snoozeSchedule } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(
|
||||
createdAlert.id,
|
||||
snoozeSchedule.schedule.id
|
||||
);
|
||||
|
||||
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: getUnauthorizedErrorMessage(
|
||||
'get',
|
||||
'test.unrestricted-noop',
|
||||
'alertsFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
|
@ -236,7 +326,27 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id);
|
||||
const { body: snoozeSchedule } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(
|
||||
createdAlert.id,
|
||||
snoozeSchedule.schedule.id
|
||||
);
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
|
@ -244,13 +354,20 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte
|
|||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: getUnauthorizedErrorMessage('unsnooze', 'test.restricted-noop', 'alerts'),
|
||||
message: getUnauthorizedErrorMessage('get', 'test.restricted-noop', 'alerts'),
|
||||
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: getUnauthorizedErrorMessage('get', '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',
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* 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 { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
|
||||
import { UserAtSpaceScenarios } from '../../../scenarios';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import {
|
||||
AlertUtils,
|
||||
checkAAD,
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
getUnauthorizedErrorMessage,
|
||||
} from '../../../../common/lib';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createUnsnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('unsnooze_internal', () => {
|
||||
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 unsnooze 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.getUnsnoozeInternalRequest(createdAlert.id);
|
||||
|
||||
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: getUnauthorizedErrorMessage('unsnooze', '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_schedule).to.eql([]);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle unsnooze 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.getUnsnoozeInternalRequest(createdAlert.id);
|
||||
|
||||
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: getUnauthorizedErrorMessage(
|
||||
'unsnooze',
|
||||
'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_schedule).to.eql([]);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle unsnooze 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.getUnsnoozeInternalRequest(createdAlert.id);
|
||||
|
||||
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: getUnauthorizedErrorMessage(
|
||||
'unsnooze',
|
||||
'test.unrestricted-noop',
|
||||
'alertsFixture'
|
||||
),
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
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_schedule).to.eql([]);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle unsnooze 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.getUnsnoozeInternalRequest(createdAlert.id);
|
||||
|
||||
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: getUnauthorizedErrorMessage('unsnooze', '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: getUnauthorizedErrorMessage('unsnooze', 'test.restricted-noop', 'alerts'),
|
||||
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_schedule).to.eql([]);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: space.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -23,6 +23,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
|
|||
loadTestFile(require.resolve('./snooze_internal'));
|
||||
loadTestFile(require.resolve('./snooze'));
|
||||
loadTestFile(require.resolve('./unsnooze'));
|
||||
loadTestFile(require.resolve('./unsnooze_internal'));
|
||||
loadTestFile(require.resolve('./bulk_edit'));
|
||||
loadTestFile(require.resolve('./bulk_disable'));
|
||||
loadTestFile(require.resolve('./capped_action_type'));
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
export default function createSnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const NOW = new Date().toISOString();
|
||||
|
||||
describe('unsnooze', function () {
|
||||
this.tags('skipFIPS');
|
||||
|
@ -60,7 +61,29 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
|
|||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id);
|
||||
const { body: snoozeSchedule } = await supertest
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(
|
||||
createdAlert.id,
|
||||
snoozeSchedule.schedule.id
|
||||
);
|
||||
|
||||
expect(response.statusCode).to.eql(204);
|
||||
expect(response.body).to.eql('');
|
||||
|
@ -79,5 +102,65 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
|
|||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should return 400 for when rule has no snooze schedule', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id, 'random_id');
|
||||
|
||||
expect(response.statusCode).to.eql(400);
|
||||
expect(response.body.message).to.eql('Rule has no snooze schedules.');
|
||||
});
|
||||
|
||||
it('should return 404 for when invalid snooze schedule id', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
enabled: false,
|
||||
actions: [],
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}/snooze_schedule`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('content-type', 'application/json')
|
||||
.send({
|
||||
schedule: {
|
||||
custom: {
|
||||
duration: '240h',
|
||||
start: NOW,
|
||||
recurring: {
|
||||
occurrences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await alertUtils.getUnsnoozeRequest(createdAlert.id, 'random_id');
|
||||
|
||||
expect(response.statusCode).to.eql(404);
|
||||
expect(response.body.message).to.eql('Rule has no snooze schedule with id random_id.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
|
||||
import { Spaces } from '../../../scenarios';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import {
|
||||
AlertUtils,
|
||||
checkAAD,
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
} from '../../../../common/lib';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createSnoozeRuleTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('unsnooze_internal', function () {
|
||||
this.tags('skipFIPS');
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
|
||||
after(() => objectRemover.removeAll());
|
||||
|
||||
const alertUtils = new AlertUtils({ space: Spaces.space1, supertestWithoutAuth });
|
||||
|
||||
it('should handle unsnooze 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.getUnsnoozeInternalRequest(createdAlert.id);
|
||||
|
||||
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.is_snoozed_until).to.eql(null);
|
||||
expect(updatedAlert.snooze_schedule.length).to.eql(0);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue