[8.11] Http versioning rule tags (#165849) (#168609)

# Backport

This will backport the following commits from `main` to `8.11`:
- [Http versioning rule tags
(#165849)](https://github.com/elastic/kibana/pull/165849)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Julian
Gernun","email":"17549662+jcger@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-10-11T13:59:58Z","message":"Http
versioning rule tags (#165849)\n\n## Summary\r\n\r\nMeta Issue:
https://github.com/elastic/kibana/issues/157883\r\nAdd HTTP versioning
to GET rule tags
endpoint","sha":"0e1ed719ec229b3fcf1351cb82ff175342379fab","branchLabelMapping":{"^v8.12.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","v8.11.0","v8.12.0"],"number":165849,"url":"https://github.com/elastic/kibana/pull/165849","mergeCommit":{"message":"Http
versioning rule tags (#165849)\n\n## Summary\r\n\r\nMeta Issue:
https://github.com/elastic/kibana/issues/157883\r\nAdd HTTP versioning
to GET rule tags
endpoint","sha":"0e1ed719ec229b3fcf1351cb82ff175342379fab"}},"sourceBranch":"main","suggestedTargetBranches":["8.11"],"targetPullRequestStates":[{"branch":"8.11","label":"v8.11.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.12.0","labelRegex":"^v8.12.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/165849","number":165849,"mergeCommit":{"message":"Http
versioning rule tags (#165849)\n\n## Summary\r\n\r\nMeta Issue:
https://github.com/elastic/kibana/issues/157883\r\nAdd HTTP versioning
to GET rule tags
endpoint","sha":"0e1ed719ec229b3fcf1351cb82ff175342379fab"}}]}]
BACKPORT-->

Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-10-11 11:23:28 -04:00 committed by GitHub
parent f21dceacaa
commit b01320b419
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 286 additions and 92 deletions

View file

@ -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 { DEFAULT_TAGS_PER_PAGEV1 as DEFAULT_TAGS_PER_PAGE } from './v1';

View file

@ -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 const DEFAULT_TAGS_PER_PAGEV1 = 50 as const;

View file

@ -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.
*/
export { ruleTagsRequestQuerySchema, ruleTagsFormattedResponseSchema } from './schemas/latest';
export type { RuleTagsRequestQuery, RuleTagsFormattedResponse } from './types/latest';
export {
ruleTagsRequestQuerySchema as ruleTagsRequestQuerySchemaV1,
ruleTagsFormattedResponseSchema as ruleTagsFormattedResponseSchemaV1,
} from './schemas/v1';
export type {
RuleTagsRequestQuery as RuleTagsRequestQueryV1,
RuleTagsFormattedResponse as RuleTagsFormattedResponseV1,
} from './types/v1';

View file

@ -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 { ruleTagsRequestQuerySchema, ruleTagsFormattedResponseSchema } from './v1';

View file

@ -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';
import { DEFAULT_TAGS_PER_PAGEV1 } from '../constants/v1';
export const ruleTagsRequestQuerySchema = schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
per_page: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGEV1, min: 1 })),
search: schema.maybe(schema.string()),
});
export const ruleTagsFormattedResponseSchema = schema.object({
total: schema.number(),
page: schema.number(),
perPage: schema.number(),
data: schema.arrayOf(schema.string()),
});

View file

@ -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 type { RuleTagsRequestQuery, RuleTagsFormattedResponse } from './v1';

View file

@ -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 { ruleTagsRequestQuerySchema, ruleTagsFormattedResponseSchema } from '..';
export type RuleTagsRequestQuery = TypeOf<typeof ruleTagsRequestQuerySchema>;
export type RuleTagsFormattedResponse = TypeOf<typeof ruleTagsFormattedResponseSchema>;

View file

@ -5,23 +5,23 @@
* 2.0.
*/
import { v4 } from 'uuid';
import { RulesClient, ConstructorOptions } from '../rules_client';
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
import {
savedObjectsClientMock,
loggingSystemMock,
savedObjectsRepositoryMock,
} 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 } from './lib';
import { RecoveredActionGroup } from '../../../common';
import { RegistryRuleType } from '../../rule_type_registry';
import { getBeforeSetup } from '../../../../rules_client/tests/lib';
import { RecoveredActionGroup } from '../../../../../common';
import { RegistryRuleType } from '../../../../rule_type_registry';
const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create();

View file

@ -5,49 +5,26 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { TypeOf, schema } from '@kbn/config-schema';
import { KueryNode, nodeBuilder, nodeTypes } from '@kbn/es-query';
import { RulesClientContext } from '../types';
import { AlertingAuthorizationEntity } from '../../authorization';
import { alertingAuthorizationFilterOpts } from '../common/constants';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { RawRule } from '../../types';
import { findRulesSo } from '../../../../data/rule/methods/find_rules_so';
import { ruleTagsParamsSchema, RuleTagsParams, RuleTagsAggregationResult } from '.';
import type { RuleTagsFormattedResponse } from '../../../../../common/routes/rule/apis/tags';
import { DEFAULT_TAGS_PER_PAGE } from '../../../../../common/routes/rule/apis/tags/constants/latest';
import { RulesClientContext } from '../../../../rules_client/types';
import { AlertingAuthorizationEntity } from '../../../../authorization';
import { alertingAuthorizationFilterOpts } from '../../../../rules_client/common/constants';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
export const DEFAULT_TAGS_PER_PAGE = 50;
const MAX_TAGS = 10000;
const getTagsParamsSchema = schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
perPage: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGE, min: 1 })),
search: schema.maybe(schema.string()),
});
export type GetTagsParams = TypeOf<typeof getTagsParamsSchema>;
export interface RuleTagsAggregationResult {
tags: {
buckets: Array<{
key: string;
doc_count: number;
}>;
};
}
export interface GetTagsResult {
total: number;
page: number;
perPage: number;
data: string[];
}
export async function getTags(
export async function getRuleTags(
context: RulesClientContext,
params: GetTagsParams
): Promise<GetTagsResult> {
let validatedParams: GetTagsParams;
params: RuleTagsParams
): Promise<RuleTagsFormattedResponse> {
let validatedParams: RuleTagsParams;
try {
validatedParams = getTagsParamsSchema.validate(params);
validatedParams = ruleTagsParamsSchema.validate(params);
} catch (error) {
throw Boom.badRequest(`Failed to validate params: ${error.message}`);
}
@ -80,20 +57,19 @@ export async function getTags(
])
: authorizationFilter;
const response = await context.unsecuredSavedObjectsClient.find<
RawRule,
RuleTagsAggregationResult
>({
filter,
type: 'alert',
aggs: {
tags: {
terms: {
field: 'alert.attributes.tags',
order: {
_key: 'asc',
const response = await findRulesSo<RuleTagsAggregationResult>({
savedObjectsClient: context.unsecuredSavedObjectsClient,
savedObjectsFindOptions: {
filter,
aggs: {
tags: {
terms: {
field: 'alert.attributes.tags',
order: {
_key: 'asc',
},
size: MAX_TAGS,
},
size: MAX_TAGS,
},
},
},

View 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.
*/
export { ruleTagsParamsSchema, ruleTagsAggregationResultSchema } from './schemas';
export type { RuleTagsParams, RuleTagsAggregationResult } from './types';
export { getRuleTags } from './get_rule_tags';

View file

@ -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 { ruleTagsParamsSchema } from './rule_tags_params_schema';
export { ruleTagsAggregationResultSchema } from './rule_tags_aggregation_result_schema';

View 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 { schema } from '@kbn/config-schema';
export const ruleTagsAggregationResultSchema = schema.object({
tags: schema.object({
buckets: schema.arrayOf(
schema.object({
key: schema.string(),
doc_count: schema.number(),
})
),
}),
});

View 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';
import { DEFAULT_TAGS_PER_PAGE } from '../../../../../../common/routes/rule/apis/tags/constants/latest';
export const ruleTagsParamsSchema = schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
perPage: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGE, min: 1 })),
search: schema.maybe(schema.string()),
});

View file

@ -0,0 +1,9 @@
/*
* 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 { RuleTagsParams } from './rule_tags_params';
export type { RuleTagsAggregationResult } from './rule_tags_aggregation_result';

View file

@ -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 { ruleTagsAggregationResultSchema } from '../schemas';
export type RuleTagsAggregationResult = TypeOf<typeof ruleTagsAggregationResultSchema>;

View 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 { TypeOf } from '@kbn/config-schema';
import { ruleTagsParamsSchema } from '../schemas';
export type RuleTagsParams = TypeOf<typeof ruleTagsParamsSchema>;

View file

@ -46,7 +46,7 @@ import { bulkDisableRulesRoute } from './rule/apis/bulk_disable/bulk_disable_rul
import { cloneRuleRoute } from './clone_rule';
import { getFlappingSettingsRoute } from './get_flapping_settings';
import { updateFlappingSettingsRoute } from './update_flapping_settings';
import { getRuleTagsRoute } from './get_rule_tags';
import { getRuleTagsRoute } from './rule/apis/tags/get_rule_tags';
import { getScheduleFrequencyRoute } from './rule/apis/get_schedule_frequency';
import { bulkUntrackAlertRoute } from './rule/apis/bulk_untrack';

View file

@ -6,15 +6,15 @@
*/
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { rulesClientMock } from '../rules_client.mock';
import { licenseStateMock } from '../../../../lib/license_state.mock';
import { verifyApiAccess } from '../../../../lib/license_api_access';
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
import { rulesClientMock } from '../../../../rules_client.mock';
import { getRuleTagsRoute } from './get_rule_tags';
const rulesClient = rulesClientMock.create();
jest.mock('../lib/license_api_access', () => ({
jest.mock('../../../../lib/license_api_access', () => ({
verifyApiAccess: jest.fn(),
}));

View file

@ -4,33 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { IRouter } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types';
import { ILicenseState } from '../lib';
import { RewriteResponseCase, RewriteRequestCase, verifyAccessAndContext } from './lib';
import { transformRuleTagsBodyResponseV1 } from './transforms';
import { transformRuleTagsQueryRequestV1 } from './transforms';
import {
DEFAULT_TAGS_PER_PAGE,
GetTagsParams,
GetTagsResult,
} from '../rules_client/methods/get_tags';
const querySchema = schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
per_page: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGE, min: 1 })),
search: schema.maybe(schema.string()),
});
const rewriteQueryReq: RewriteRequestCase<GetTagsParams> = ({ per_page: perPage, ...rest }) => ({
...rest,
perPage,
});
const rewriteBodyRes: RewriteResponseCase<GetTagsResult> = ({ perPage, ...rest }) => ({
...rest,
per_page: perPage,
});
ruleTagsRequestQuerySchemaV1,
RuleTagsRequestQueryV1,
} from '../../../../../common/routes/rule/apis/tags';
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types';
import { ILicenseState } from '../../../../lib';
import { verifyAccessAndContext } from '../../../lib';
export const getRuleTagsRoute = (
router: IRouter<AlertingRequestHandlerContext>,
@ -40,18 +23,20 @@ export const getRuleTagsRoute = (
{
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_tags`,
validate: {
query: querySchema,
query: ruleTagsRequestQuerySchemaV1,
},
},
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const rulesClient = (await context.alerting).getRulesClient();
const options = rewriteQueryReq(req.query);
const query: RuleTagsRequestQueryV1 = req.query;
const options = transformRuleTagsQueryRequestV1(query);
const tagsResult = await rulesClient.getTags(options);
return res.ok({
body: rewriteBodyRes(tagsResult),
body: transformRuleTagsBodyResponseV1(tagsResult),
});
})
)

View file

@ -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 { getRuleTagsRoute } from './get_rule_tags';

View file

@ -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.
*/
export { transformRuleTagsQueryRequest } from './transform_rule_tags_query_request/latest';
export { transformRuleTagsQueryRequest as transformRuleTagsQueryRequestV1 } from './transform_rule_tags_query_request/v1';
export { transformRuleTagsBodyResponse } from './transform_rule_tags_body_response/latest';
export { transformRuleTagsBodyResponse as transformRuleTagsBodyResponseV1 } from './transform_rule_tags_body_response/v1';

View file

@ -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 { transformRuleTagsBodyResponse } from './v1';

View file

@ -0,0 +1,20 @@
/*
* 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 { RewriteResponseCase } from '@kbn/actions-plugin/common';
import type { RuleTagsFormattedResponse } from '../../../../../../../common/routes/rule/apis/tags';
export const transformRuleTagsBodyResponse: RewriteResponseCase<RuleTagsFormattedResponse> = ({
perPage,
total,
page,
data,
}) => ({
total,
page,
data,
per_page: perPage,
});

View file

@ -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 { transformRuleTagsQueryRequest } from './v1';

View 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 { RewriteRequestCase } from '@kbn/actions-plugin/common';
import type { RuleTagsParams } from '../../../../../../application/rule/methods/tags';
export const transformRuleTagsQueryRequest: RewriteRequestCase<RuleTagsParams> = ({
per_page: perPage,
page,
search,
}) => ({
page,
search,
perPage,
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { getRuleTags, RuleTagsParams } from '../application/rule/methods/tags';
import { MuteAlertParams } from '../application/rule/methods/mute_alert/types';
import { SanitizedRule, RuleTypeParams } from '../types';
import { parseDuration } from '../../common/parse_duration';
@ -63,7 +64,6 @@ import { unmuteInstance } from './methods/unmute_instance';
import { runSoon } from './methods/run_soon';
import { listRuleTypes } from './methods/list_rule_types';
import { getAlertFromRaw, GetAlertFromRawParams } from './lib/get_alert_from_raw';
import { getTags, GetTagsParams } from './methods/get_tags';
import { getScheduleFrequency } from '../application/rule/methods/get_schedule_frequency/get_schedule_frequency';
import {
bulkUntrackAlerts,
@ -193,7 +193,7 @@ export class RulesClient {
return this.context.auditLogger;
}
public getTags = (params: GetTagsParams) => getTags(this.context, params);
public getTags = (params: RuleTagsParams) => getRuleTags(this.context, params);
public getScheduleFrequency = () => getScheduleFrequency(this.context);