mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ResponseOps][Rules] Version unmute alert route (#188830)
## Summary Versions the `POST /api/alerting/rule/{rule_id}/alert/{alert_id}/_unmute` enpoint. ## References Parent issue: https://github.com/elastic/kibana/issues/187572 Closes #187574 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Antonio <antoniodcoelho@gmail.com>
This commit is contained in:
parent
fa6d4aa97f
commit
d88a1b4ede
19 changed files with 227 additions and 74 deletions
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { unmuteAlertParamsSchema } from './schemas/latest';
|
||||
export { unmuteAlertParamsSchema as unmuteAlertParamsSchemaV1 } from './schemas/v1';
|
||||
|
||||
export type { UnmuteAlertRequestParams } from './types/latest';
|
||||
export type { UnmuteAlertRequestParams as UnmuteAlertRequestParamsV1 } from './types/v1';
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { unmuteAlertParamsSchema } from './v1';
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 unmuteAlertParamsSchema = schema.object({
|
||||
rule_id: schema.string({
|
||||
meta: {
|
||||
description: 'The identifier for the rule.',
|
||||
},
|
||||
}),
|
||||
alert_id: schema.string({
|
||||
meta: {
|
||||
description: 'The identifier for the alert.',
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -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 type { UnmuteAlertRequestParams } from './v1';
|
|
@ -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 { unmuteAlertParamsSchemaV1 } from '..';
|
||||
|
||||
export type UnmuteAlertRequestParams = TypeOf<typeof unmuteAlertParamsSchemaV1>;
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { unmuteAlertParamsSchema } from './unmute_alert_params_schema';
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 unmuteAlertParamsSchema = schema.object({
|
||||
alertId: schema.string(),
|
||||
alertInstanceId: schema.string(),
|
||||
});
|
|
@ -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 type { UnmuteAlertParams } from './unmute_alert_params';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { TypeOf } from '@kbn/config-schema';
|
||||
import { unmuteAlertParamsSchema } from '../schemas';
|
||||
|
||||
export type UnmuteAlertParams = TypeOf<typeof unmuteAlertParamsSchema>;
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RulesClient, ConstructorOptions } from '../rules_client';
|
||||
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
loggingSystemMock,
|
||||
|
@ -13,17 +13,17 @@ import {
|
|||
uiSettingsServiceMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
|
||||
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
|
||||
import { AlertingAuthorization } from '../../../../authorization/alerting_authorization';
|
||||
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
|
||||
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
|
||||
import { getBeforeSetup, setGlobalDate } from './lib';
|
||||
import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
|
||||
import { backfillClientMock } from '../../backfill_client/backfill_client.mock';
|
||||
import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib';
|
||||
import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
|
||||
import { backfillClientMock } from '../../../../backfill_client/backfill_client.mock';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
|
@ -203,6 +203,17 @@ describe('unmuteInstance()', () => {
|
|||
ruleTypeId: 'myType',
|
||||
});
|
||||
});
|
||||
|
||||
test('throws an error if API params do not match the schema', async () => {
|
||||
const rulesClient = new RulesClient(rulesClientParams);
|
||||
await expect(
|
||||
// @ts-expect-error: Wrong params for testing purposes
|
||||
rulesClient.unmuteInstance({ alertId: 1 })
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to validate params: [alertId]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
expect(unsecuredSavedObjectsClient.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('auditLogger', () => {
|
|
@ -5,39 +5,43 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Rule, RawRule } from '../../types';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
|
||||
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
|
||||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
||||
import { MuteOptions } from '../types';
|
||||
import { RulesClientContext } from '../types';
|
||||
import { updateMeta } from '../lib';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
|
||||
import Boom from '@hapi/boom';
|
||||
import type { Rule } from '../../../../types';
|
||||
import type { RulesClientContext } from '../../../../rules_client/types';
|
||||
import type { UnmuteAlertParams } from './types';
|
||||
import { retryIfConflicts } from '../../../../lib/retry_if_conflicts';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
|
||||
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
|
||||
import { unmuteAlertParamsSchema } from './schemas';
|
||||
import { updateMeta } from '../../../../rules_client/lib';
|
||||
import { updateRuleSo } from '../../../../data/rule';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization';
|
||||
|
||||
export async function unmuteInstance(
|
||||
context: RulesClientContext,
|
||||
{ alertId, alertInstanceId }: MuteOptions
|
||||
params: UnmuteAlertParams
|
||||
): Promise<void> {
|
||||
const ruleId = params.alertId;
|
||||
try {
|
||||
unmuteAlertParamsSchema.validate(params);
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Failed to validate params: ${error.message}`);
|
||||
}
|
||||
|
||||
return await retryIfConflicts(
|
||||
context.logger,
|
||||
`rulesClient.unmuteInstance('${alertId}')`,
|
||||
async () => await unmuteInstanceWithOCC(context, { alertId, alertInstanceId })
|
||||
`rulesClient.unmuteInstance('${ruleId}')`,
|
||||
async () => await unmuteInstanceWithOCC(context, params)
|
||||
);
|
||||
}
|
||||
|
||||
async function unmuteInstanceWithOCC(
|
||||
context: RulesClientContext,
|
||||
{
|
||||
alertId,
|
||||
alertInstanceId,
|
||||
}: {
|
||||
alertId: string;
|
||||
alertInstanceId: string;
|
||||
}
|
||||
{ alertId: ruleId, alertInstanceId }: UnmuteAlertParams
|
||||
) {
|
||||
const { attributes, version } = await context.unsecuredSavedObjectsClient.get<Rule>(
|
||||
RULE_SAVED_OBJECT_TYPE,
|
||||
alertId
|
||||
ruleId
|
||||
);
|
||||
|
||||
try {
|
||||
|
@ -54,7 +58,7 @@ async function unmuteInstanceWithOCC(
|
|||
context.auditLogger?.log(
|
||||
ruleAuditEvent({
|
||||
action: RuleAuditAction.UNMUTE_ALERT,
|
||||
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: alertId },
|
||||
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: ruleId },
|
||||
error,
|
||||
})
|
||||
);
|
||||
|
@ -65,7 +69,7 @@ async function unmuteInstanceWithOCC(
|
|||
ruleAuditEvent({
|
||||
action: RuleAuditAction.UNMUTE_ALERT,
|
||||
outcome: 'unknown',
|
||||
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: alertId },
|
||||
savedObject: { type: RULE_SAVED_OBJECT_TYPE, id: ruleId },
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -73,15 +77,15 @@ async function unmuteInstanceWithOCC(
|
|||
|
||||
const mutedInstanceIds = attributes.mutedInstanceIds || [];
|
||||
if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) {
|
||||
await context.unsecuredSavedObjectsClient.update<RawRule>(
|
||||
RULE_SAVED_OBJECT_TYPE,
|
||||
alertId,
|
||||
updateMeta(context, {
|
||||
await updateRuleSo({
|
||||
savedObjectsClient: context.unsecuredSavedObjectsClient,
|
||||
savedObjectsUpdateOptions: { version },
|
||||
id: ruleId,
|
||||
updateRuleAttributes: updateMeta(context, {
|
||||
mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId),
|
||||
updatedBy: await context.getUserName(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId),
|
||||
}),
|
||||
{ version }
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ import { ruleTypesRoute } from './rule_types';
|
|||
import { muteAllRuleRoute } from './mute_all_rule';
|
||||
import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert';
|
||||
import { unmuteAllRuleRoute } from './unmute_all_rule';
|
||||
import { unmuteAlertRoute } from './unmute_alert';
|
||||
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 { snoozeRuleRoute } from './rule/apis/snooze';
|
||||
|
|
|
@ -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 { transformRequestParamsToApplication } from './transform_request_params_to_application/latest';
|
||||
export { transformRequestParamsToApplication as transformRequestParamsToApplicationV1 } from './transform_request_params_to_application/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 { transformRequestParamsToApplication } from './v1';
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { transformRequestParamsToApplication } from '..';
|
||||
|
||||
describe('transformRequestParamsToApplication', () => {
|
||||
it('changes the parameters case', () => {
|
||||
const transformed = transformRequestParamsToApplication({
|
||||
rule_id: 'test-rule-id',
|
||||
alert_id: 'test-alert-id',
|
||||
});
|
||||
expect(transformed).toEqual({ alertId: 'test-rule-id', alertInstanceId: 'test-alert-id' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { UnmuteAlertParams } from '../../../../../../application/rule/methods/unmute_alert/types';
|
||||
import { RewriteRequestCase } from '../../../../../lib';
|
||||
import { UnmuteAlertRequestParamsV1 } from '../../../../../../../common/routes/rule/apis/unmute_alert';
|
||||
|
||||
export const transformRequestParamsToApplication: RewriteRequestCase<UnmuteAlertParams> = ({
|
||||
rule_id: alertId,
|
||||
alert_id: alertInstanceId,
|
||||
}: UnmuteAlertRequestParamsV1) => ({
|
||||
alertId,
|
||||
alertInstanceId,
|
||||
});
|
|
@ -5,15 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { unmuteAlertRoute } from './unmute_alert';
|
||||
import { unmuteAlertRoute } from './unmute_alert_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(),
|
||||
}));
|
||||
|
|
@ -6,32 +6,14 @@
|
|||
*/
|
||||
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState, RuleTypeDisabledError } from '../lib';
|
||||
import { MuteOptions } from '../rules_client';
|
||||
import { RewriteRequestCase, verifyAccessAndContext } from './lib';
|
||||
import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
rule_id: schema.string({
|
||||
meta: {
|
||||
description: 'The identifier for the rule.',
|
||||
},
|
||||
}),
|
||||
alert_id: schema.string({
|
||||
meta: {
|
||||
description: 'The identifier for the alert.',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const rewriteParamsReq: RewriteRequestCase<MuteOptions> = ({
|
||||
rule_id: alertId,
|
||||
alert_id: alertInstanceId,
|
||||
}) => ({
|
||||
alertId,
|
||||
alertInstanceId,
|
||||
});
|
||||
import { ILicenseState, RuleTypeDisabledError } from '../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../lib';
|
||||
import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../../../../types';
|
||||
import {
|
||||
unmuteAlertParamsSchemaV1,
|
||||
UnmuteAlertRequestParamsV1,
|
||||
} from '../../../../../common/routes/rule/apis/unmute_alert';
|
||||
import { transformRequestParamsToApplicationV1 } from './transforms';
|
||||
|
||||
export const unmuteAlertRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
|
@ -45,15 +27,22 @@ export const unmuteAlertRoute = (
|
|||
summary: `Unmute an alert`,
|
||||
},
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
request: {
|
||||
params: unmuteAlertParamsSchemaV1,
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'Indicates a successful call.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = (await context.alerting).getRulesClient();
|
||||
const params = rewriteParamsReq(req.params);
|
||||
const params: UnmuteAlertRequestParamsV1 = req.params;
|
||||
try {
|
||||
await rulesClient.unmuteInstance(params);
|
||||
await rulesClient.unmuteInstance(transformRequestParamsToApplicationV1(params));
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { UnmuteAlertParams } from '../application/rule/methods/unmute_alert/types';
|
||||
import { getRuleTags, RuleTagsParams } from '../application/rule/methods/tags';
|
||||
import { MuteAlertParams } from '../application/rule/methods/mute_alert/types';
|
||||
import { SanitizedRule, RuleTypeParams } from '../types';
|
||||
|
@ -60,7 +61,7 @@ import { clearExpiredSnoozes } from './methods/clear_expired_snoozes';
|
|||
import { muteInstance } from '../application/rule/methods/mute_alert/mute_instance';
|
||||
import { muteAll } from './methods/mute_all';
|
||||
import { unmuteAll } from './methods/unmute_all';
|
||||
import { unmuteInstance } from './methods/unmute_instance';
|
||||
import { unmuteInstance } from '../application/rule/methods/unmute_alert/unmute_instance';
|
||||
import { runSoon } from './methods/run_soon';
|
||||
import { listRuleTypes } from './methods/list_rule_types';
|
||||
import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw';
|
||||
|
@ -181,7 +182,7 @@ export class RulesClient {
|
|||
public muteAll = (options: { id: string }) => muteAll(this.context, options);
|
||||
public unmuteAll = (options: { id: string }) => unmuteAll(this.context, options);
|
||||
public muteInstance = (options: MuteAlertParams) => muteInstance(this.context, options);
|
||||
public unmuteInstance = (options: MuteAlertParams) => unmuteInstance(this.context, options);
|
||||
public unmuteInstance = (options: UnmuteAlertParams) => unmuteInstance(this.context, options);
|
||||
|
||||
public bulkUntrackAlerts = (options: BulkUntrackBody) => bulkUntrackAlerts(this.context, options);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue