From dd55c99284a354cc25362767f1d7fe09ce94e384 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Mar 2025 03:56:24 +1100 Subject: [PATCH] [8.x] [Security Solution] Reduce the _review rule upgrade endpoint response size (#211045) (#212921) # Backport This will backport the following commits from `main` to `8.x`: - [[Security Solution] Reduce the _review rule upgrade endpoint response size (#211045)](https://github.com/elastic/kibana/pull/211045) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) Co-authored-by: Dmitrii Shevchenko --- .../common/prebuilt_rules_filter.ts | 29 +++ .../review_prebuilt_rules_upgrade_filter.ts | 24 ++ .../get_prebuilt_rules_status_route.ts | 12 + .../detection_engine/prebuilt_rules/index.ts | 1 + .../perform_rule_upgrade_route.ts | 13 +- .../review_rule_upgrade_route.ts | 65 ++++- .../security_solution/common/constants.ts | 10 +- .../rule_management/rule_fields.ts | 1 + .../rule_management/rule_filtering.ts | 13 +- .../rule_management/api/api.ts | 25 +- .../use_fetch_prebuilt_rules_status_query.ts | 12 +- ...tch_prebuilt_rules_upgrade_review_query.ts | 10 +- ... => use_perform_rules_upgrade_mutation.ts} | 30 +-- .../use_perform_rule_upgrade.ts | 12 +- .../use_prebuilt_rules_upgrade_review.ts | 8 +- .../rule_management/logic/types.ts | 12 +- .../rule_update_callouts.tsx | 4 +- .../add_prebuilt_rules_table_context.tsx | 2 +- .../rules_table/rules_table_toolbar.tsx | 2 +- .../translations.tsx | 7 - .../upgrade_prebuilt_rules_table.tsx | 43 +++- .../upgrade_prebuilt_rules_table_buttons.tsx | 28 +-- .../upgrade_prebuilt_rules_table_context.tsx | 202 +++++++++++---- .../upgrade_prebuilt_rules_table_filters.tsx | 36 ++- ...rade_rule_customization_filter_popover.tsx | 35 ++- .../use_filter_prebuilt_rules_to_upgrade.ts | 48 ---- .../use_prebuilt_rules_upgrade_state.test.ts | 1 + .../add_elastic_rules_button.tsx | 2 +- .../get_prebuilt_rules_status_route.ts | 30 ++- .../get_upgradeable_rules.test.ts | 191 -------------- .../get_upgradeable_rules.ts | 85 ------- .../perform_rule_upgrade_handler.ts | 235 +++++++++++++++++ .../perform_rule_upgrade_route.ts | 109 +------- .../review_rule_installation_handler.ts | 20 +- .../calculate_rule_upgrade_info.ts | 59 +++++ .../review_rule_upgrade_handler.ts | 170 +++++++------ .../review_rule_upgrade_route.ts | 18 +- .../prebuilt_rules/constants.ts | 8 + .../prebuilt_rule_objects_client.ts | 136 +++++++++- .../fetch_rule_versions_triad.ts | 16 +- .../search/get_existing_prepackaged_rules.ts | 14 +- .../preview_prebuilt_rules_upgrade.ts | 236 ------------------ .../update_prebuilt_rules_package.ts | 11 - .../install_update_error_handling.cy.ts | 6 + .../update_workflow_customized_rules.cy.ts | 8 - .../cypress/tasks/api_calls/prebuilt_rules.ts | 12 +- .../cypress/tasks/prebuilt_rules.ts | 11 +- 47 files changed, 1045 insertions(+), 1017 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/prebuilt_rules_filter.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/review_prebuilt_rules_upgrade_filter.ts rename x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/{use_perform_specific_rules_upgrade_mutation.ts => use_perform_rules_upgrade_mutation.ts} (79%) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/calculate_rule_upgrade_info.ts diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/prebuilt_rules_filter.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/prebuilt_rules_filter.ts new file mode 100644 index 0000000000000..12a5fc449f571 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/prebuilt_rules_filter.ts @@ -0,0 +1,29 @@ +/* + * 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 { z } from '@kbn/zod'; + +export enum RuleCustomizationStatus { + CUSTOMIZED = 'CUSTOMIZED', + NOT_CUSTOMIZED = 'NOT_CUSTOMIZED', +} + +export type PrebuiltRulesFilter = z.infer; +export const PrebuiltRulesFilter = z.object({ + /** + * Tags to filter by + */ + tags: z.array(z.string()).optional(), + /** + * Rule name to filter by + */ + name: z.string().optional(), + /** + * Rule customization status to filter by + */ + customization_status: z.nativeEnum(RuleCustomizationStatus).optional(), +}); diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/review_prebuilt_rules_upgrade_filter.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/review_prebuilt_rules_upgrade_filter.ts new file mode 100644 index 0000000000000..8b99489fb4bb4 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/common/review_prebuilt_rules_upgrade_filter.ts @@ -0,0 +1,24 @@ +/* + * 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 { z } from '@kbn/zod'; +import { PrebuiltRulesFilter } from './prebuilt_rules_filter'; + +export enum RuleCustomizationStatus { + CUSTOMIZED = 'CUSTOMIZED', + NOT_CUSTOMIZED = 'NOT_CUSTOMIZED', +} + +export type ReviewPrebuiltRuleUpgradeFilter = z.infer; +export const ReviewPrebuiltRuleUpgradeFilter = PrebuiltRulesFilter.merge( + z.object({ + /** + * Rule IDs to return upgrade info for + */ + rule_ids: z.array(z.string()).optional(), + }) +); diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts index e76ba63cfa17d..1a9e70f84cd2c 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts @@ -8,6 +8,18 @@ export interface GetPrebuiltRulesStatusResponseBody { /** Aggregated info about all prebuilt rules */ stats: PrebuiltRulesStatusStats; + + /** + * Aggregated info about upgradeable prebuilt rules. This fields is optional + * for backward compatibility. After one serverless release cycle, it can be + * made required. + * */ + aggregated_fields?: { + upgradeable_rules: { + /** List of all tags of the current versions of upgradeable rules */ + tags: string[]; + }; + }; } export interface PrebuiltRulesStatusStats { diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts index 02ab5c8a3cc0c..df1b5851f5474 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts @@ -22,3 +22,4 @@ export * from './model/diff/three_way_diff/three_way_diff_outcome'; export * from './model/diff/three_way_diff/three_way_diff'; export * from './model/diff/three_way_diff/three_way_diff_conflict'; export * from './model/diff/three_way_diff/three_way_merge_outcome'; +export * from './common/prebuilt_rules_filter'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts index ac75bbb56e0bf..de3bb4fc27e1a 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -10,6 +10,7 @@ import { mapValues } from 'lodash'; import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; import { AggregatedPrebuiltRuleError, DiffableAllFields } from '../model'; import { RuleSignatureId, RuleVersion } from '../../model'; +import { PrebuiltRulesFilter } from '../common/prebuilt_rules_filter'; export type Mode = z.infer; export const Mode = z.enum(['ALL_RULES', 'SPECIFIC_RULES']); @@ -111,21 +112,31 @@ export const RuleUpgradeSpecifier = z.object({ fields: RuleFieldsToUpgrade.optional(), }); +export type UpgradeConflictResolution = z.infer; +export const UpgradeConflictResolution = z.enum(['SKIP', 'OVERWRITE']); +export type UpgradeConflictResolutionEnum = typeof UpgradeConflictResolution.enum; +export const UpgradeConflictResolutionEnum = UpgradeConflictResolution.enum; + export type UpgradeSpecificRulesRequest = z.infer; export const UpgradeSpecificRulesRequest = z.object({ mode: z.literal('SPECIFIC_RULES'), rules: z.array(RuleUpgradeSpecifier).min(1), pick_version: PickVersionValues.optional(), + on_conflict: UpgradeConflictResolution.optional(), + dry_run: z.boolean().optional(), }); export type UpgradeAllRulesRequest = z.infer; export const UpgradeAllRulesRequest = z.object({ mode: z.literal('ALL_RULES'), pick_version: PickVersionValues.optional(), + filter: PrebuiltRulesFilter.optional(), + on_conflict: UpgradeConflictResolution.optional(), + dry_run: z.boolean().optional(), }); export type SkipRuleUpgradeReason = z.infer; -export const SkipRuleUpgradeReason = z.enum(['RULE_UP_TO_DATE']); +export const SkipRuleUpgradeReason = z.enum(['RULE_UP_TO_DATE', 'CONFLICT']); export type SkipRuleUpgradeReasonEnum = typeof SkipRuleUpgradeReason.enum; export const SkipRuleUpgradeReasonEnum = SkipRuleUpgradeReason.enum; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts index 2f2d6e3bd1c26..3fd95412b9311 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts @@ -5,35 +5,86 @@ * 2.0. */ -import type { RuleObjectId, RuleSignatureId, RuleTagArray } from '../../model'; +import { z } from '@kbn/zod'; +import { SortOrder, type RuleObjectId, type RuleSignatureId, type RuleTagArray } from '../../model'; import type { PartialRuleDiff } from '../model'; -import type { RuleResponse } from '../../model/rule_schema'; +import type { RuleResponse, RuleVersion } from '../../model/rule_schema'; +import { FindRulesSortField } from '../../rule_management'; +import { PrebuiltRulesFilter } from '../common/prebuilt_rules_filter'; + +export type ReviewRuleUpgradeSort = z.infer; +export const ReviewRuleUpgradeSort = z.object({ + /** + * Field to sort by + */ + field: FindRulesSortField.optional(), + /** + * Sort order + */ + order: SortOrder.optional(), +}); + +export type ReviewRuleUpgradeRequestBody = z.infer; +export const ReviewRuleUpgradeRequestBody = z + .object({ + filter: PrebuiltRulesFilter.optional(), + sort: ReviewRuleUpgradeSort.optional(), + + page: z.coerce.number().int().min(1).optional().default(1), + /** + * Rules per page + */ + per_page: z.coerce.number().int().min(0).optional().default(20), + }) + .nullable(); export interface ReviewRuleUpgradeResponseBody { - /** Aggregated info about all rules available for upgrade */ + /** + * @deprecated Use the prebuilt rule status API instead. The field is kept + * here for backward compatibility but can be removed after one Serverless + * release. + */ stats: RuleUpgradeStatsForReview; /** Info about individual rules: one object per each rule available for upgrade */ rules: RuleUpgradeInfoForReview[]; + + /** The requested page number */ + page: number; + + /** The requested number of items per page */ + per_page: number; + + /** The total number of rules available for upgrade that match the filter criteria */ + total: number; } export interface RuleUpgradeStatsForReview { - /** Number of installed prebuilt rules available for upgrade (stock + customized) */ + /** + * @deprecated Always 0 + */ num_rules_to_upgrade_total: number; - /** Number of installed prebuilt rules with upgrade conflicts (SOLVABLE or NON_SOLVABLE) */ + /** + * @deprecated Always 0 + */ num_rules_with_conflicts: number; - /** Number of installed prebuilt rules with NON_SOLVABLE upgrade conflicts */ + /** + * @deprecated Always 0 + */ num_rules_with_non_solvable_conflicts: number; - /** A union of all tags of all rules available for upgrade */ + /** + * @deprecated Always an empty array + */ tags: RuleTagArray; } export interface RuleUpgradeInfoForReview { id: RuleObjectId; rule_id: RuleSignatureId; + version: RuleVersion; current_rule: RuleResponse; target_rule: RuleResponse; diff: PartialRuleDiff; diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index 43a09d8221d62..80768ab80aa81 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -388,7 +388,7 @@ export const STARTED_TRANSFORM_STATES = new Set([ ]); /** - * How many rules to update at a time is set to 50 from errors coming from + * How many rules to update at a time is set to 20 from errors coming from * the slow environments such as cloud when the rule updates are > 100 we were * seeing timeout issues. * @@ -403,14 +403,14 @@ export const STARTED_TRANSFORM_STATES = new Set([ * Lastly, we saw weird issues where Chrome on upstream 408 timeouts will re-call the REST route * which in turn could create additional connections we want to avoid. * - * See file import_rules_route.ts for another area where 50 was chosen, therefore I chose - * 50 here to mimic it as well. If you see this re-opened or what similar to it, consider - * reducing the 50 above to a lower number. + * See file import_rules_route.ts for another area where 20 was chosen, therefore I chose + * 20 here to mimic it as well. If you see this re-opened or what similar to it, consider + * reducing the 20 above to a lower number. * * See the original ticket here: * https://github.com/elastic/kibana/issues/94418 */ -export const MAX_RULES_TO_UPDATE_IN_PARALLEL = 50; +export const MAX_RULES_TO_UPDATE_IN_PARALLEL = 20; export const LIMITED_CONCURRENCY_ROUTE_TAG_PREFIX = `${APP_ID}:limitedConcurrency`; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_fields.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_fields.ts index 5d72cd15a96ae..610b2231e8ee7 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_fields.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_fields.ts @@ -22,3 +22,4 @@ export const TAGS_FIELD = 'alert.attributes.tags'; export const PARAMS_TYPE_FIELD = 'alert.attributes.params.type'; export const PARAMS_IMMUTABLE_FIELD = 'alert.attributes.params.immutable'; export const LAST_RUN_OUTCOME_FIELD = 'alert.attributes.lastRun.outcome'; +export const IS_CUSTOMIZED_FIELD = 'alert.attributes.params.ruleSource.isCustomized'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_filtering.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_filtering.ts index 52ace0cfac5da..692f2fa55a5e5 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_filtering.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/rule_management/rule_filtering.ts @@ -7,10 +7,11 @@ import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { RuleExecutionStatus } from '../../api/detection_engine'; -import { RuleExecutionStatusEnum } from '../../api/detection_engine'; +import { RuleCustomizationStatus, RuleExecutionStatusEnum } from '../../api/detection_engine'; import { prepareKQLStringParam } from '../../utils/kql'; import { ENABLED_FIELD, + IS_CUSTOMIZED_FIELD, LAST_RUN_OUTCOME_FIELD, PARAMS_IMMUTABLE_FIELD, PARAMS_TYPE_FIELD, @@ -23,6 +24,8 @@ export const KQL_FILTER_IMMUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: true`; export const KQL_FILTER_MUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: false`; export const KQL_FILTER_ENABLED_RULES = `${ENABLED_FIELD}: true`; export const KQL_FILTER_DISABLED_RULES = `${ENABLED_FIELD}: false`; +export const KQL_FILTER_CUSTOMIZED_RULES = `${IS_CUSTOMIZED_FIELD}: true`; +export const KQL_FILTER_NOT_CUSTOMIZED_RULES = `${IS_CUSTOMIZED_FIELD}: false`; interface RulesFilterOptions { filter: string; @@ -32,6 +35,7 @@ interface RulesFilterOptions { tags: string[]; excludeRuleTypes: Type[]; ruleExecutionStatus: RuleExecutionStatus; + customizationStatus: RuleCustomizationStatus; ruleIds: string[]; } @@ -50,6 +54,7 @@ export function convertRulesFilterToKQL({ tags, excludeRuleTypes = [], ruleExecutionStatus, + customizationStatus, }: Partial): string { const kql: string[] = []; @@ -85,6 +90,12 @@ export function convertRulesFilterToKQL({ kql.push(`${LAST_RUN_OUTCOME_FIELD}: "failed"`); } + if (customizationStatus === RuleCustomizationStatus.CUSTOMIZED) { + kql.push(KQL_FILTER_CUSTOMIZED_RULES); + } else if (customizationStatus === RuleCustomizationStatus.NOT_CUSTOMIZED) { + kql.push(KQL_FILTER_NOT_CUSTOMIZED_RULES); + } + return kql.join(' AND '); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 5f5fded87ac45..400a18a8103b9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -15,14 +15,14 @@ import type { ActionType, AsApiContract } from '@kbn/actions-plugin/common'; import type { ActionResult } from '@kbn/actions-plugin/server'; import { convertRulesFilterToKQL } from '../../../../common/detection_engine/rule_management/rule_filtering'; import type { - UpgradeSpecificRulesRequest, - PickVersionValues, PerformRuleUpgradeResponseBody, InstallSpecificRulesRequest, PerformRuleInstallationResponseBody, GetPrebuiltRulesStatusResponseBody, ReviewRuleUpgradeResponseBody, ReviewRuleInstallationResponseBody, + ReviewRuleUpgradeRequestBody, + PerformRuleUpgradeRequestBody, } from '../../../../common/api/detection_engine/prebuilt_rules'; import type { BulkDuplicateRules, @@ -637,13 +637,16 @@ export const getPrebuiltRulesStatus = async ({ */ export const reviewRuleUpgrade = async ({ signal, + request, }: { signal: AbortSignal | undefined; + request: ReviewRuleUpgradeRequestBody; }): Promise => KibanaServices.get().http.fetch(REVIEW_RULE_UPGRADE_URL, { method: 'POST', version: '1', signal, + body: JSON.stringify(request), }); /** @@ -685,23 +688,13 @@ export const performInstallSpecificRules = async ( }), }); -export interface PerformUpgradeRequest { - rules: UpgradeSpecificRulesRequest['rules']; - pickVersion: PickVersionValues; -} - -export const performUpgradeSpecificRules = async ({ - rules, - pickVersion, -}: PerformUpgradeRequest): Promise => +export const performUpgradeRules = async ( + body: PerformRuleUpgradeRequestBody +): Promise => KibanaServices.get().http.fetch(PERFORM_RULE_UPGRADE_URL, { method: 'POST', version: '1', - body: JSON.stringify({ - mode: 'SPECIFIC_RULES', - rules, - pick_version: pickVersion, - }), + body: JSON.stringify(body), }); export const bootstrapPrebuiltRules = async (): Promise => diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query.ts index 0c0515e61b818..376877326a5e9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query.ts @@ -4,24 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useCallback } from 'react'; import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import type { PrebuiltRulesStatusStats } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { useCallback } from 'react'; +import type { GetPrebuiltRulesStatusResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { GET_PREBUILT_RULES_STATUS_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { getPrebuiltRulesStatus } from '../../api'; import { DEFAULT_QUERY_OPTIONS } from '../constants'; -import { GET_PREBUILT_RULES_STATUS_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; export const PREBUILT_RULES_STATUS_QUERY_KEY = ['GET', GET_PREBUILT_RULES_STATUS_URL]; export const useFetchPrebuiltRulesStatusQuery = ( - options?: UseQueryOptions + options?: UseQueryOptions ) => { - return useQuery( + return useQuery( PREBUILT_RULES_STATUS_QUERY_KEY, async ({ signal }) => { const response = await getPrebuiltRulesStatus({ signal }); - return response.stats; + return response; }, { ...DEFAULT_QUERY_OPTIONS, diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query.ts index 532114b1d4b62..4b779918febc3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query.ts @@ -9,7 +9,10 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { reviewRuleUpgrade } from '../../api'; import { REVIEW_RULE_UPGRADE_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules/urls'; -import type { ReviewRuleUpgradeResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { + ReviewRuleUpgradeRequestBody, + ReviewRuleUpgradeResponseBody, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { DEFAULT_QUERY_OPTIONS } from '../constants'; import { retryOnRateLimitedError } from './retry_on_rate_limited_error'; import { cappedExponentialBackoff } from './capped_exponential_backoff'; @@ -17,12 +20,13 @@ import { cappedExponentialBackoff } from './capped_exponential_backoff'; export const REVIEW_RULE_UPGRADE_QUERY_KEY = ['POST', REVIEW_RULE_UPGRADE_URL]; export const useFetchPrebuiltRulesUpgradeReviewQuery = ( + request: ReviewRuleUpgradeRequestBody, options?: UseQueryOptions ) => { return useQuery( - REVIEW_RULE_UPGRADE_QUERY_KEY, + [...REVIEW_RULE_UPGRADE_QUERY_KEY, request], async ({ signal }) => { - const response = await reviewRuleUpgrade({ signal }); + const response = await reviewRuleUpgrade({ signal, request }); return response; }, { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_specific_rules_upgrade_mutation.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_rules_upgrade_mutation.ts similarity index 79% rename from x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_specific_rules_upgrade_mutation.ts rename to x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_rules_upgrade_mutation.ts index 84b074449603d..ecb604d63d4e0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_specific_rules_upgrade_mutation.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_perform_rules_upgrade_mutation.ts @@ -6,10 +6,12 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import type { PerformRuleUpgradeResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { + PerformRuleUpgradeRequestBody, + PerformRuleUpgradeResponseBody, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { PERFORM_RULE_UPGRADE_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules/urls'; -import type { PerformUpgradeRequest } from '../../api'; -import { performUpgradeSpecificRules } from '../../api'; +import { performUpgradeRules } from '../../api'; import { useInvalidateFetchCoverageOverviewQuery } from '../use_fetch_coverage_overview_query'; import { useInvalidateFetchRuleManagementFiltersQuery } from '../use_fetch_rule_management_filters_query'; import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../use_fetch_rules_snooze_settings_query'; @@ -19,14 +21,14 @@ import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './use_fetch_p import { retryOnRateLimitedError } from './retry_on_rate_limited_error'; import { cappedExponentialBackoff } from './capped_exponential_backoff'; -export const PERFORM_SPECIFIC_RULES_UPGRADE_KEY = [ - 'POST', - 'SPECIFIC_RULES', - PERFORM_RULE_UPGRADE_URL, -]; +export const PERFORM_RULES_UPGRADE_KEY = ['POST', PERFORM_RULE_UPGRADE_URL]; -export const usePerformSpecificRulesUpgradeMutation = ( - options?: UseMutationOptions +export const usePerformRulesUpgradeMutation = ( + options?: UseMutationOptions< + PerformRuleUpgradeResponseBody, + unknown, + PerformRuleUpgradeRequestBody + > ) => { const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); const invalidateFetchRulesSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery(); @@ -37,13 +39,13 @@ export const usePerformSpecificRulesUpgradeMutation = ( const invalidateRuleStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); const invalidateFetchCoverageOverviewQuery = useInvalidateFetchCoverageOverviewQuery(); - return useMutation( - (args: PerformUpgradeRequest) => { - return performUpgradeSpecificRules(args); + return useMutation( + (args: PerformRuleUpgradeRequestBody) => { + return performUpgradeRules(args); }, { ...options, - mutationKey: PERFORM_SPECIFIC_RULES_UPGRADE_KEY, + mutationKey: PERFORM_RULES_UPGRADE_KEY, onSettled: (...args) => { invalidatePrePackagedRulesStatus(); invalidateFindRulesQuery(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts index 33f36ffe14da2..e1ae2768dacc6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts @@ -5,18 +5,22 @@ * 2.0. */ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { usePerformSpecificRulesUpgradeMutation } from '../../api/hooks/prebuilt_rules/use_perform_specific_rules_upgrade_mutation'; +import { usePerformRulesUpgradeMutation } from '../../api/hooks/prebuilt_rules/use_perform_rules_upgrade_mutation'; import * as i18n from './translations'; -export const usePerformUpgradeSpecificRules = () => { +export const usePerformUpgradeRules = () => { const { addError, addSuccess } = useAppToasts(); - return usePerformSpecificRulesUpgradeMutation({ + return usePerformRulesUpgradeMutation({ onError: (err) => { addError(err, { title: i18n.RULE_UPGRADE_FAILED }); }, - onSuccess: (result) => { + onSuccess: (result, vars) => { + if (vars.dry_run) { + // This is a preflight check, no need to show toast + return; + } addSuccess(getSuccessToastMessage(result)); }, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review.ts index 6e8f008c5ede5..bb0b04174d0dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review.ts @@ -5,7 +5,10 @@ * 2.0. */ import type { UseQueryOptions } from '@tanstack/react-query'; -import type { ReviewRuleUpgradeResponseBody } from '../../../../../common/api/detection_engine/prebuilt_rules'; +import type { + ReviewRuleUpgradeRequestBody, + ReviewRuleUpgradeResponseBody, +} from '../../../../../common/api/detection_engine/prebuilt_rules'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import * as i18n from '../translations'; @@ -18,11 +21,12 @@ import { useFetchPrebuiltRulesUpgradeReviewQuery } from '../../api/hooks/prebuil * @returns useQuery result */ export const usePrebuiltRulesUpgradeReview = ( + request: ReviewRuleUpgradeRequestBody, options?: UseQueryOptions ) => { const { addError } = useAppToasts(); - return useFetchPrebuiltRulesUpgradeReviewQuery({ + return useFetchPrebuiltRulesUpgradeReviewQuery(request, { onError: (error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }), ...options, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index e91639dd0454c..98383e93b444c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -11,7 +11,10 @@ import type { RuleSnooze } from '@kbn/alerting-plugin/common'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; -import type { WarningSchema } from '../../../../common/api/detection_engine'; +import type { + RuleCustomizationStatus, + WarningSchema, +} from '../../../../common/api/detection_engine'; import type { RuleExecutionStatus } from '../../../../common/api/detection_engine/rule_monitoring'; import { SortOrder } from '../../../../common/api/detection_engine'; @@ -103,7 +106,7 @@ export interface FilterOptions { excludeRuleTypes?: Type[]; enabled?: boolean; // undefined is to display all the rules ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all" - ruleSource?: RuleCustomizationEnum[]; // undefined is to display all the rules + ruleSource?: RuleCustomizationStatus[]; // undefined is to display all the rules showRulesWithGaps?: boolean; gapSearchRange?: GapRangeValue; } @@ -209,8 +212,3 @@ export interface FindRulesReferencedByExceptionsProps { lists: FindRulesReferencedByExceptionsListProp[]; signal?: AbortSignal; } - -export enum RuleCustomizationEnum { - customized = 'CUSTOMIZED', - not_customized = 'NOT_CUSTOMIZED', -} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_update_callouts/rule_update_callouts.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_update_callouts/rule_update_callouts.tsx index c4eacb4a01ff5..6ca59501dbd7a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_update_callouts/rule_update_callouts.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_update_callouts/rule_update_callouts.tsx @@ -21,8 +21,8 @@ import { AllRulesTabs } from '../rules_table/rules_table_toolbar'; export const RuleUpdateCallouts = () => { const { data: prebuiltRulesStatus } = usePrebuiltRulesStatus(); - const rulesToInstallCount = prebuiltRulesStatus?.num_prebuilt_rules_to_install ?? 0; - const rulesToUpgradeCount = prebuiltRulesStatus?.num_prebuilt_rules_to_upgrade ?? 0; + const rulesToInstallCount = prebuiltRulesStatus?.stats.num_prebuilt_rules_to_install ?? 0; + const rulesToUpgradeCount = prebuiltRulesStatus?.stats.num_prebuilt_rules_to_upgrade ?? 0; // Check against rulesInstalledCount since we don't want to show banners if we're showing the empty prompt const shouldDisplayNewRulesCallout = rulesToInstallCount > 0; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx index bb949ba436995..59d66a402dc88 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx @@ -144,7 +144,7 @@ export const AddPrebuiltRulesTableContextProvider = ({ enabled: isUpgradeReviewRequestEnabled({ canUserCRUD, isUpgradingSecurityPackages, - prebuiltRulesStatus, + prebuiltRulesStatus: prebuiltRulesStatus?.stats, }), }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx index e3618a1383598..eb9d17ebfbe9c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx @@ -33,7 +33,7 @@ export const RulesTableToolbar = React.memo(() => { const installedTotal = (ruleManagementFilters?.rules_summary.custom_count ?? 0) + (ruleManagementFilters?.rules_summary.prebuilt_installed_count ?? 0); - const updateTotal = prebuiltRulesStatus?.num_prebuilt_rules_to_upgrade ?? 0; + const updateTotal = prebuiltRulesStatus?.stats.num_prebuilt_rules_to_upgrade ?? 0; const shouldDisplayRuleUpdatesTab = !loading && canUserCRUD && updateTotal > 0; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/translations.tsx index 73fb4e9381d73..a5b2754e939ad 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/translations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/translations.tsx @@ -34,13 +34,6 @@ export const BULK_UPDATE_BUTTON_TOOLTIP_NO_PERMISSIONS = i18n.translate( } ); -export const BULK_UPDATE_ALL_RULES_BUTTON_TOOLTIP_CONFLICTS = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.upgradeRules.bulkButtons.allRules.conflicts', - { - defaultMessage: 'All rules have conflicts. Update them individually.', - } -); - export const BULK_UPDATE_SELECTED_RULES_BUTTON_TOOLTIP_CONFLICTS = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.upgradeRules.bulkButtons.selectedRules.conflicts', { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx index dfe8c5787417c..959f20ef72c20 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx @@ -10,7 +10,7 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, + EuiBasicTable, EuiProgress, EuiSkeletonLoading, EuiSkeletonText, @@ -19,9 +19,10 @@ import { import React, { useCallback, useState } from 'react'; import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; -import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants'; +import { RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants'; import { RulesChangelogLink } from '../rules_changelog_link'; import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons'; +import type { UpgradePrebuiltRulesSortingOptions } from './upgrade_prebuilt_rules_table_context'; import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; import { UpgradePrebuiltRulesTableFilters } from './upgrade_prebuilt_rules_table_filters'; import { useUpgradePrebuiltRulesTableColumns } from './use_upgrade_prebuilt_rules_table_columns'; @@ -44,20 +45,32 @@ export const UpgradePrebuiltRulesTable = React.memo(() => { ruleUpgradeStates, hasRulesToUpgrade, isLoading, + isFetching, isRefetching, isUpgradingSecurityPackages, + pagination, + sortingOptions, }, + actions: { setPagination, setSortingOptions }, } = useUpgradePrebuiltRulesTableContext(); const [selected, setSelected] = useState([]); const rulesColumns = useUpgradePrebuiltRulesTableColumns(); const shouldShowProgress = isUpgradingSecurityPackages || isRefetching; - const [pageIndex, setPageIndex] = useState(0); const handleTableChange = useCallback( - ({ page: { index } }: CriteriaWithPagination) => { - setPageIndex(index); + ({ page: { index, size }, sort }: CriteriaWithPagination) => { + setPagination({ + page: index + 1, + perPage: size, + }); + if (sort) { + setSortingOptions({ + field: sort.field as UpgradePrebuiltRulesSortingOptions['field'], + order: sort.direction, + }); + } }, - [setPageIndex] + [setPagination, setSortingOptions] ); return ( @@ -104,23 +117,31 @@ export const UpgradePrebuiltRulesTable = React.memo(() => { - true, onSelectionChange: setSelected, initialSelected: selected, }} + sorting={{ + sort: { + // EuiBasicTable has incorrect `sort.field` types which accept only `keyof Item` and reject fields in dot notation + field: sortingOptions.field as keyof RuleUpgradeState, + direction: sortingOptions.order, + }, + }} itemId="rule_id" data-test-subj="rules-upgrades-table" columns={rulesColumns} - onTableChange={handleTableChange} + onChange={handleTableChange} /> ) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx index 37e189f5e4b79..5498e8961c06a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx @@ -21,13 +21,7 @@ export const UpgradePrebuiltRulesTableButtons = ({ selectedRules, }: UpgradePrebuiltRulesTableButtonsProps) => { const { - state: { - ruleUpgradeStates, - hasRulesToUpgrade, - loadingRules, - isRefetching, - isUpgradingSecurityPackages, - }, + state: { hasRulesToUpgrade, loadingRules, isRefetching, isUpgradingSecurityPackages }, actions: { upgradeRules, upgradeAllRules }, } = useUpgradePrebuiltRulesTableContext(); const { isRulesCustomizationEnabled } = usePrebuiltRulesCustomizationStatus(); @@ -43,14 +37,10 @@ export const UpgradePrebuiltRulesTableButtons = ({ const doAllSelectedRulesHaveConflicts = isRulesCustomizationEnabled && selectedRules.every(({ hasUnresolvedConflicts }) => hasUnresolvedConflicts); - const doAllRulesHaveConflicts = - isRulesCustomizationEnabled && - ruleUpgradeStates.every(({ hasUnresolvedConflicts }) => hasUnresolvedConflicts); const { selectedRulesButtonTooltip, allRulesButtonTooltip } = useBulkUpdateButtonsTooltipContent({ canUserEditRules, doAllSelectedRulesHaveConflicts, - doAllRulesHaveConflicts, isPrebuiltRulesCustomizationEnabled: isRulesCustomizationEnabled, }); @@ -83,12 +73,7 @@ export const UpgradePrebuiltRulesTableButtons = ({ fill iconType="plusInCircle" onClick={upgradeAllRules} - disabled={ - !canUserEditRules || - !hasRulesToUpgrade || - isRequestInProgress || - doAllRulesHaveConflicts - } + disabled={!canUserEditRules || !hasRulesToUpgrade || isRequestInProgress} data-test-subj="upgradeAllRulesButton" > {i18n.UPDATE_ALL} @@ -103,12 +88,10 @@ export const UpgradePrebuiltRulesTableButtons = ({ const useBulkUpdateButtonsTooltipContent = ({ canUserEditRules, doAllSelectedRulesHaveConflicts, - doAllRulesHaveConflicts, isPrebuiltRulesCustomizationEnabled, }: { canUserEditRules: boolean | null; doAllSelectedRulesHaveConflicts: boolean; - doAllRulesHaveConflicts: boolean; isPrebuiltRulesCustomizationEnabled: boolean; }) => { if (!canUserEditRules) { @@ -125,13 +108,6 @@ const useBulkUpdateButtonsTooltipContent = ({ }; } - if (doAllRulesHaveConflicts) { - return { - selectedRulesButtonTooltip: i18n.BULK_UPDATE_SELECTED_RULES_BUTTON_TOOLTIP_CONFLICTS, - allRulesButtonTooltip: i18n.BULK_UPDATE_ALL_RULES_BUTTON_TOOLTIP_CONFLICTS, - }; - } - if (doAllSelectedRulesHaveConflicts) { return { selectedRulesButtonTooltip: i18n.BULK_UPDATE_SELECTED_RULES_BUTTON_TOOLTIP_CONFLICTS, diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index 3be956c7cbf94..b0c7f93234c99 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -10,8 +10,11 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status'; import type { + FindRulesSortField, + PrebuiltRulesFilter, RuleFieldsToUpgrade, RuleUpgradeSpecifier, + SortOrder, } from '../../../../../../common/api/detection_engine'; import { usePrebuiltRulesCustomizationStatus } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_customization_status'; import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; @@ -24,13 +27,11 @@ import type { } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { invariant } from '../../../../../../common/utils/invariant'; import { TabContentPadding } from '../../../../rule_management/components/rule_details/rule_details_flyout'; -import { usePerformUpgradeSpecificRules } from '../../../../rule_management/logic/prebuilt_rules/use_perform_rule_upgrade'; +import { usePerformUpgradeRules } from '../../../../rule_management/logic/prebuilt_rules/use_perform_rule_upgrade'; import { usePrebuiltRulesUpgradeReview } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review'; import { RuleDiffTab } from '../../../../rule_management/components/rule_details/rule_diff_tab'; import { FieldUpgradeStateEnum } from '../../../../rule_management/model/prebuilt_rule_upgrade/field_upgrade_state_enum'; import { useRulePreviewFlyout } from '../use_rule_preview_flyout'; -import type { UpgradePrebuiltRulesTableFilterOptions } from './use_filter_prebuilt_rules_to_upgrade'; -import { useFilterPrebuiltRulesToUpgrade } from './use_filter_prebuilt_rules_to_upgrade'; import { usePrebuiltRulesUpgradeState } from './use_prebuilt_rules_upgrade_state'; import { useOutdatedMlJobsUpgradeModal } from './use_ml_jobs_upgrade_modal'; import { useUpgradeWithConflictsModal } from './use_upgrade_with_conflicts_modal'; @@ -39,9 +40,21 @@ import { UpgradeFlyoutSubHeader } from './upgrade_flyout_subheader'; import * as ruleDetailsI18n from '../../../../rule_management/components/rule_details/translations'; import * as i18n from './translations'; import { CustomizationDisabledCallout } from './customization_disabled_callout'; +import { RULES_TABLE_INITIAL_PAGE_SIZE } from '../constants'; +import type { PaginationOptions } from '../../../../rule_management/logic'; +import { usePrebuiltRulesStatus } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_status'; const REVIEW_PREBUILT_RULES_UPGRADE_REFRESH_INTERVAL = 5 * 60 * 1000; +export interface UpgradePrebuiltRulesSortingOptions { + field: + | 'current_rule.name' + | 'current_rule.risk_score' + | 'current_rule.severity' + | 'current_rule.last_updated'; + order: SortOrder; +} + export interface UpgradePrebuiltRulesTableState { /** * Rule upgrade state after applying `filterOptions` @@ -50,7 +63,7 @@ export interface UpgradePrebuiltRulesTableState { /** * Currently selected table filter */ - filterOptions: UpgradePrebuiltRulesTableFilterOptions; + filterOptions: PrebuiltRulesFilter; /** * All unique tags for all rules */ @@ -63,6 +76,10 @@ export interface UpgradePrebuiltRulesTableState { * Is true then there is no cached data and the query is currently fetching. */ isLoading: boolean; + /** + * Is true whenever a request is in-flight, which includes initial loading as well as background refetches. + */ + isFetching: boolean; /** * Will be true if the query has been fetched. */ @@ -84,6 +101,14 @@ export interface UpgradePrebuiltRulesTableState { * The timestamp for when the rules were successfully fetched */ lastUpdated: number; + /** + * Current pagination state + */ + pagination: PaginationOptions; + /** + * Currently selected table sorting + */ + sortingOptions: UpgradePrebuiltRulesSortingOptions; } export const PREBUILT_RULE_UPDATE_FLYOUT_ANCHOR = 'updatePrebuiltRulePreview'; @@ -92,7 +117,9 @@ export interface UpgradePrebuiltRulesTableActions { reFetchRules: () => void; upgradeRules: (ruleIds: RuleSignatureId[]) => void; upgradeAllRules: () => void; - setFilterOptions: Dispatch>; + setFilterOptions: Dispatch>; + setPagination: Dispatch>; + setSortingOptions: Dispatch>; openRulePreview: (ruleId: string) => void; } @@ -121,35 +148,71 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ }: UpgradePrebuiltRulesTableContextProviderProps) => { const { isRulesCustomizationEnabled, customizationDisabledReason } = usePrebuiltRulesCustomizationStatus(); + + // Use the data from the prebuilt rules status API to determine if there are + // rules to upgrade because it returns information about all rules without filters + const { data: prebuiltRulesStatusResponse } = usePrebuiltRulesStatus(); + const hasRulesToUpgrade = + (prebuiltRulesStatusResponse?.stats.num_prebuilt_rules_to_upgrade ?? 0) > 0; + const tags = prebuiltRulesStatusResponse?.aggregated_fields?.upgradeable_rules.tags; + const [loadingRules, setLoadingRules] = useState([]); - const [filterOptions, setFilterOptions] = useState({ - filter: '', - tags: [], - ruleSource: [], - }); + const [filterOptions, setFilterOptions] = useState({}); const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages(); + const [pagination, setPagination] = useState({ + page: 1, + perPage: RULES_TABLE_INITIAL_PAGE_SIZE, + }); + const [sortingOptions, setSortingOptions] = useState({ + field: 'current_rule.last_updated', + order: 'asc', + }); + + const findRulesSortField = useMemo( + () => + (( + { + 'current_rule.name': 'name', + 'current_rule.risk_score': 'risk_score', + 'current_rule.severity': 'severity', + 'current_rule.last_updated': 'updated_at', + } as const + )[sortingOptions.field]), + [sortingOptions.field] + ); const { - data: { rules: ruleUpgradeInfos, stats: { tags } } = { - rules: [], - stats: { tags: [] }, - }, + data: upgradeReviewResponse, refetch, dataUpdatedAt, isFetched, isLoading, + isFetching, isRefetching, - } = usePrebuiltRulesUpgradeReview({ - refetchInterval: REVIEW_PREBUILT_RULES_UPGRADE_REFRESH_INTERVAL, - keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change - }); + } = usePrebuiltRulesUpgradeReview( + { + page: pagination.page, + per_page: pagination.perPage, + sort: { + field: findRulesSortField, + order: sortingOptions.order, + }, + filter: filterOptions, + }, + { + refetchInterval: REVIEW_PREBUILT_RULES_UPGRADE_REFRESH_INTERVAL, + keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change + } + ); + + const upgradeableRules = useMemo( + () => upgradeReviewResponse?.rules ?? [], + [upgradeReviewResponse] + ); + const { rulesUpgradeState, setRuleFieldResolvedValue } = - usePrebuiltRulesUpgradeState(ruleUpgradeInfos); + usePrebuiltRulesUpgradeState(upgradeableRules); const ruleUpgradeStates = useMemo(() => Object.values(rulesUpgradeState), [rulesUpgradeState]); - const filteredRuleUpgradeStates = useFilterPrebuiltRulesToUpgrade({ - filterOptions, - data: ruleUpgradeStates, - }); const { modal: confirmLegacyMlJobsUpgradeModal, @@ -158,7 +221,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ } = useOutdatedMlJobsUpgradeModal(); const { modal: upgradeConflictsModal, confirmConflictsUpgrade } = useUpgradeWithConflictsModal(); - const { mutateAsync: upgradeSpecificRulesRequest } = usePerformUpgradeSpecificRules(); + const { mutateAsync: upgradeRulesRequest } = usePerformUpgradeRules(); const upgradeRulesToResolved = useCallback( async (ruleIds: RuleSignatureId[]) => { @@ -189,8 +252,9 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ return; } - await upgradeSpecificRulesRequest({ - pickVersion: 'MERGED', + await upgradeRulesRequest({ + mode: 'SPECIFIC_RULES', + pick_version: 'MERGED', rules: ruleUpgradeSpecifiers, }); } catch { @@ -201,7 +265,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setLoadingRules((prev) => prev.filter((id) => !upgradedRuleIdsSet.has(id))); } }, - [confirmLegacyMLJobs, confirmConflictsUpgrade, rulesUpgradeState, upgradeSpecificRulesRequest] + [confirmLegacyMLJobs, confirmConflictsUpgrade, rulesUpgradeState, upgradeRulesRequest] ); const upgradeRulesToTarget = useCallback( @@ -220,8 +284,9 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ return; } - await upgradeSpecificRulesRequest({ - pickVersion: 'TARGET', + await upgradeRulesRequest({ + mode: 'SPECIFIC_RULES', + pick_version: 'TARGET', rules: ruleUpgradeSpecifiers, }); } catch { @@ -232,7 +297,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setLoadingRules((prev) => prev.filter((id) => !upgradedRuleIdsSet.has(id))); } }, - [confirmLegacyMLJobs, rulesUpgradeState, upgradeSpecificRulesRequest] + [confirmLegacyMLJobs, rulesUpgradeState, upgradeRulesRequest] ); const upgradeRules = useCallback( @@ -246,11 +311,50 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ [isRulesCustomizationEnabled, upgradeRulesToResolved, upgradeRulesToTarget] ); - const upgradeAllRules = useCallback( - // Upgrade all rules, ignoring filter and selection - () => upgradeRules(ruleUpgradeInfos.map((rule) => rule.rule_id)), - [ruleUpgradeInfos, upgradeRules] - ); + const upgradeAllRules = useCallback(async () => { + setLoadingRules((prev) => [...prev, ...upgradeableRules.map((rule) => rule.rule_id)]); + + try { + // Handle MLJobs modal + if (!(await confirmLegacyMLJobs())) { + return; + } + + const dryRunResults = await upgradeRulesRequest({ + mode: 'ALL_RULES', + pick_version: isRulesCustomizationEnabled ? 'MERGED' : 'TARGET', + filter: filterOptions, + dry_run: true, + on_conflict: 'SKIP', + }); + + const hasConflicts = dryRunResults.results.skipped.some( + (skippedRule) => skippedRule.reason === 'CONFLICT' + ); + + if (hasConflicts && !(await confirmConflictsUpgrade())) { + return; + } + + await upgradeRulesRequest({ + mode: 'ALL_RULES', + pick_version: isRulesCustomizationEnabled ? 'MERGED' : 'TARGET', + filter: filterOptions, + on_conflict: 'SKIP', + }); + } catch { + // Error is handled by the mutation's onError callback, so no need to do anything here + } finally { + setLoadingRules([]); + } + }, [ + upgradeableRules, + confirmLegacyMLJobs, + upgradeRulesRequest, + isRulesCustomizationEnabled, + filterOptions, + confirmConflictsUpgrade, + ]); const subHeaderFactory = useCallback( (rule: RuleResponse) => @@ -377,12 +481,8 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setRuleFieldResolvedValue, ] ); - const filteredRules = useMemo( - () => filteredRuleUpgradeStates.map(({ target_rule: targetRule }) => targetRule), - [filteredRuleUpgradeStates] - ); const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ - rules: filteredRules, + rules: ruleUpgradeStates.map(({ target_rule: targetRule }) => targetRule), subHeaderFactory, ruleActionsFactory, extraTabsFactory, @@ -399,6 +499,8 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ upgradeAllRules, setFilterOptions, openRulePreview, + setPagination, + setSortingOptions, }), [refetch, upgradeRules, upgradeAllRules, openRulePreview] ); @@ -406,31 +508,41 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ const providerValue = useMemo( () => ({ state: { - ruleUpgradeStates: filteredRuleUpgradeStates, - hasRulesToUpgrade: isFetched && ruleUpgradeInfos.length > 0, + ruleUpgradeStates, + hasRulesToUpgrade, filterOptions, - tags, + tags: tags ?? [], isFetched, isLoading: isLoading || areMlJobsLoading, + isFetching, isRefetching, isUpgradingSecurityPackages, loadingRules, lastUpdated: dataUpdatedAt, + pagination: { + ...pagination, + total: upgradeReviewResponse?.total ?? 0, + }, + sortingOptions, }, actions, }), [ - ruleUpgradeInfos.length, - filteredRuleUpgradeStates, + ruleUpgradeStates, + hasRulesToUpgrade, filterOptions, tags, isFetched, isLoading, areMlJobsLoading, + isFetching, isRefetching, isUpgradingSecurityPackages, loadingRules, dataUpdatedAt, + pagination, + upgradeReviewResponse?.total, + sortingOptions, actions, ] ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx index d8b8f618cf43d..b62f987b8abe2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx @@ -9,11 +9,11 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; +import type { RuleCustomizationStatus } from '../../../../../../common/api/detection_engine'; import { usePrebuiltRulesCustomizationStatus } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_customization_status'; -import type { RuleCustomizationEnum } from '../../../../rule_management/logic'; -import * as i18n from './translations'; -import { TagsFilterPopover } from '../rules_table_filters/tags_filter_popover'; import { RuleSearchField } from '../rules_table_filters/rule_search_field'; +import { TagsFilterPopover } from '../rules_table_filters/tags_filter_popover'; +import * as i18n from './translations'; import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; import { RuleCustomizationFilterPopover } from './upgrade_rule_customization_filter_popover'; @@ -33,13 +33,13 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { const { isRulesCustomizationEnabled } = usePrebuiltRulesCustomizationStatus(); - const { tags: selectedTags, ruleSource: selectedRuleSource = [] } = filterOptions; + const { tags: selectedTags, customization_status: customizationStatus } = filterOptions; const handleOnSearch = useCallback( - (filterString: string) => { + (nameString: string) => { setFilterOptions((filters) => ({ ...filters, - filter: filterString.trim(), + name: nameString.trim(), })); }, [setFilterOptions] @@ -57,22 +57,20 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { [selectedTags, setFilterOptions] ); - const handleSelectedRuleSource = useCallback( - (newRuleSource: RuleCustomizationEnum[]) => { - if (!isEqual(newRuleSource, selectedRuleSource)) { - setFilterOptions((filters) => ({ - ...filters, - ruleSource: newRuleSource, - })); - } + const handleCustomizationStatusChange = useCallback( + (newCustomizationStatus: RuleCustomizationStatus | undefined) => { + setFilterOptions((filters) => ({ + ...filters, + customization_status: newCustomizationStatus, + })); }, - [selectedRuleSource, setFilterOptions] + [setFilterOptions] ); return ( @@ -81,8 +79,8 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { {isRulesCustomizationEnabled && ( @@ -90,7 +88,7 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx index 234943e333272..05e68f4ebbccb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx @@ -5,23 +5,22 @@ * 2.0. */ -import React, { useState, useMemo } from 'react'; import type { EuiSelectableOption } from '@elastic/eui'; import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui'; -import { RuleCustomizationEnum } from '../../../../rule_management/logic'; +import React, { useMemo, useState } from 'react'; +import { RuleCustomizationStatus } from '../../../../../../common/api/detection_engine'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; -import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; interface RuleCustomizationFilterPopoverProps { - selectedRuleSource: RuleCustomizationEnum[]; - onSelectedRuleSourceChanged: (newRuleSource: RuleCustomizationEnum[]) => void; + customizationStatus: RuleCustomizationStatus | undefined; + onCustomizationStatusChanged: (newRuleSource: RuleCustomizationStatus | undefined) => void; } const RULE_CUSTOMIZATION_POPOVER_WIDTH = 200; const RuleCustomizationFilterPopoverComponent = ({ - selectedRuleSource, - onSelectedRuleSourceChanged, + customizationStatus, + onCustomizationStatusChanged, }: RuleCustomizationFilterPopoverProps) => { const [isRuleCustomizationPopoverOpen, setIsRuleCustomizationPopoverOpen] = useState(false); @@ -29,18 +28,16 @@ const RuleCustomizationFilterPopoverComponent = ({ () => [ { label: i18n.MODIFIED_LABEL, - key: RuleCustomizationEnum.customized, - checked: selectedRuleSource.includes(RuleCustomizationEnum.customized) ? 'on' : undefined, + key: RuleCustomizationStatus.CUSTOMIZED, + checked: customizationStatus === RuleCustomizationStatus.CUSTOMIZED ? 'on' : undefined, }, { label: i18n.UNMODIFIED_LABEL, - key: RuleCustomizationEnum.not_customized, - checked: selectedRuleSource.includes(RuleCustomizationEnum.not_customized) - ? 'on' - : undefined, + key: RuleCustomizationStatus.NOT_CUSTOMIZED, + checked: customizationStatus === RuleCustomizationStatus.NOT_CUSTOMIZED ? 'on' : undefined, }, ], - [selectedRuleSource] + [customizationStatus] ); const handleSelectableOptionsChange = ( @@ -48,10 +45,8 @@ const RuleCustomizationFilterPopoverComponent = ({ _: unknown, changedOption: EuiSelectableOption ) => { - toggleSelectedGroup( - changedOption.key ?? '', - selectedRuleSource, - onSelectedRuleSourceChanged as (args: string[]) => void + onCustomizationStatusChanged( + changedOption.checked === 'on' ? (changedOption.key as RuleCustomizationStatus) : undefined ); }; @@ -62,8 +57,8 @@ const RuleCustomizationFilterPopoverComponent = ({ onClick={() => setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} numFilters={selectableOptions.length} isSelected={isRuleCustomizationPopoverOpen} - hasActiveFilters={selectedRuleSource.length > 0} - numActiveFilters={selectedRuleSource.length} + hasActiveFilters={customizationStatus != null} + numActiveFilters={customizationStatus != null ? 1 : 0} data-test-subj="rule-customization-filter-popover-button" > {i18n.RULE_SOURCE} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts deleted file mode 100644 index f0a818fd2532e..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 { useMemo } from 'react'; -import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; -import { RuleCustomizationEnum, type FilterOptions } from '../../../../rule_management/logic/types'; - -export type UpgradePrebuiltRulesTableFilterOptions = Pick< - FilterOptions, - 'filter' | 'tags' | 'ruleSource' ->; - -interface UseFilterPrebuiltRulesToUpgradeParams { - data: RuleUpgradeState[]; - filterOptions: UpgradePrebuiltRulesTableFilterOptions; -} - -export const useFilterPrebuiltRulesToUpgrade = ({ - data, - filterOptions, -}: UseFilterPrebuiltRulesToUpgradeParams): RuleUpgradeState[] => { - return useMemo(() => { - const { filter, tags, ruleSource } = filterOptions; - - return data.filter((ruleInfo) => { - if (filter && !ruleInfo.current_rule.name.toLowerCase().includes(filter.toLowerCase())) { - return false; - } - - if (tags?.length && !tags.every((tag) => ruleInfo.current_rule.tags.includes(tag))) { - return false; - } - - if (ruleSource?.length === 1 && ruleInfo.current_rule.rule_source.type === 'external') { - if (ruleSource.includes(RuleCustomizationEnum.customized)) { - return ruleInfo.current_rule.rule_source.is_customized; - } - return ruleInfo.current_rule.rule_source.is_customized === false; - } - - return true; - }); - }, [filterOptions, data]); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.test.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.test.ts index 28b24a4efe9a5..3a35e783975e9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.test.ts @@ -429,6 +429,7 @@ function createRuleUpgradeInfoMock( num_fields_with_non_solvable_conflicts: 0, fields: {}, }, + version: 1, revision: 1, ...rewrites, }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx index d62c080ace66f..e0f87a3ee22cc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/add_elastic_rules_button.tsx @@ -33,7 +33,7 @@ export const AddElasticRulesButton = ({ }); const { data: preBuiltRulesStatus } = usePrebuiltRulesStatus(); - const newRulesCount = preBuiltRulesStatus?.num_prebuilt_rules_to_install ?? 0; + const newRulesCount = preBuiltRulesStatus?.stats.num_prebuilt_rules_to_install ?? 0; const ButtonComponent = fill ? EuiButton : EuiButtonEmpty; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts index 52e2c552c74fa..adccc7ea6488d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts @@ -12,8 +12,6 @@ import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; -import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -41,19 +39,33 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - const ruleVersionsMap = await fetchRuleVersionsTriad({ - ruleAssetsClient, - ruleObjectsClient, + const currentRuleVersions = await ruleObjectsClient.fetchInstalledRuleVersions(); + const latestRuleVersions = await ruleAssetsClient.fetchLatestVersions(); + const currentRuleVersionsMap = new Map( + currentRuleVersions.map((rule) => [rule.rule_id, rule]) + ); + const latestRuleVersionsMap = new Map( + latestRuleVersions.map((rule) => [rule.rule_id, rule]) + ); + const installableRules = latestRuleVersions.filter( + (rule) => !currentRuleVersionsMap.has(rule.rule_id) + ); + const upgradeableRules = currentRuleVersions.filter((rule) => { + const latestVersion = latestRuleVersionsMap.get(rule.rule_id); + return latestVersion != null && rule.version < latestVersion.version; }); - const { currentRules, installableRules, upgradeableRules, totalAvailableRules } = - getRuleGroups(ruleVersionsMap); const body: GetPrebuiltRulesStatusResponseBody = { stats: { - num_prebuilt_rules_installed: currentRules.length, + num_prebuilt_rules_installed: currentRuleVersions.length, num_prebuilt_rules_to_install: installableRules.length, num_prebuilt_rules_to_upgrade: upgradeableRules.length, - num_prebuilt_rules_total_in_package: totalAvailableRules.length, + num_prebuilt_rules_total_in_package: latestRuleVersions.length, + }, + aggregated_fields: { + upgradeable_rules: { + tags: [...new Set(upgradeableRules.flatMap((rule) => rule.tags))], + }, }, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts deleted file mode 100644 index 5b1c74825102c..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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 { getUpgradeableRules } from './get_upgradeable_rules'; -import { ModeEnum, SkipRuleUpgradeReasonEnum } from '../../../../../../common/api/detection_engine'; -import type { - RuleResponse, - RuleUpgradeSpecifier, -} from '../../../../../../common/api/detection_engine'; -import { getPrebuiltRuleMockOfType } from '../../model/rule_assets/prebuilt_rule_asset.mock'; -import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; -import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; - -describe('getUpgradeableRules', () => { - const baseRule = getPrebuiltRuleMockOfType('query'); - const createUpgradeableRule = ( - ruleId: string, - currentVersion: number, - targetVersion: number - ): RuleTriad => { - return { - current: { - ...baseRule, - rule_id: ruleId, - version: currentVersion, - revision: 0, - }, - target: { ...baseRule, rule_id: ruleId, version: targetVersion }, - } as RuleTriad; - }; - - const mockUpgradeableRule = createUpgradeableRule('rule-1', 1, 2); - - const mockCurrentRule: RuleResponse = { - ...convertPrebuiltRuleAssetToRuleResponse(baseRule), - rule_id: 'rule-1', - revision: 0, - version: 1, - }; - - describe('ALL_RULES mode', () => { - it('should return all upgradeable rules when in ALL_RULES mode', () => { - const result = getUpgradeableRules({ - rawUpgradeableRules: [mockUpgradeableRule], - currentRules: [mockCurrentRule], - mode: ModeEnum.ALL_RULES, - }); - - expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); - expect(result.fetchErrors).toEqual([]); - expect(result.skippedRules).toEqual([]); - }); - - it('should handle empty upgradeable rules list', () => { - const result = getUpgradeableRules({ - rawUpgradeableRules: [], - currentRules: [], - mode: ModeEnum.ALL_RULES, - }); - - expect(result.upgradeableRules).toEqual([]); - expect(result.fetchErrors).toEqual([]); - expect(result.skippedRules).toEqual([]); - }); - }); - - describe('SPECIFIC_RULES mode', () => { - const mockVersionSpecifier: RuleUpgradeSpecifier = { - rule_id: 'rule-1', - revision: 0, - version: 1, - }; - - it('should return specified upgradeable rules when in SPECIFIC_RULES mode', () => { - const result = getUpgradeableRules({ - rawUpgradeableRules: [mockUpgradeableRule], - currentRules: [mockCurrentRule], - versionSpecifiers: [mockVersionSpecifier], - mode: ModeEnum.SPECIFIC_RULES, - }); - - expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); - expect(result.fetchErrors).toEqual([]); - expect(result.skippedRules).toEqual([]); - }); - - it('should handle rule not found', () => { - const result = getUpgradeableRules({ - rawUpgradeableRules: [mockUpgradeableRule], - currentRules: [mockCurrentRule], - versionSpecifiers: [{ ...mockVersionSpecifier, rule_id: 'nonexistent' }], - mode: ModeEnum.SPECIFIC_RULES, - }); - - expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); - expect(result.fetchErrors).toHaveLength(1); - expect(result.fetchErrors[0].error.message).toContain( - 'Rule with rule_id "nonexistent" and version "1" not found' - ); - expect(result.skippedRules).toEqual([]); - }); - - it('should handle non-upgradeable rule', () => { - const nonUpgradeableRule: RuleResponse = { - ...convertPrebuiltRuleAssetToRuleResponse(baseRule), - rule_id: 'rule-2', - revision: 0, - version: 1, - }; - - const result = getUpgradeableRules({ - rawUpgradeableRules: [mockUpgradeableRule], - currentRules: [mockCurrentRule, nonUpgradeableRule], - versionSpecifiers: [mockVersionSpecifier, { ...mockVersionSpecifier, rule_id: 'rule-2' }], - mode: ModeEnum.SPECIFIC_RULES, - }); - - expect(result.upgradeableRules).toEqual([mockUpgradeableRule]); - expect(result.fetchErrors).toEqual([]); - expect(result.skippedRules).toEqual([ - { rule_id: 'rule-2', reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE }, - ]); - }); - - it('should handle revision mismatch', () => { - const result = getUpgradeableRules({ - rawUpgradeableRules: [mockUpgradeableRule], - currentRules: [mockCurrentRule], - versionSpecifiers: [{ ...mockVersionSpecifier, revision: 1 }], - mode: ModeEnum.SPECIFIC_RULES, - }); - - expect(result.upgradeableRules).toEqual([]); - expect(result.fetchErrors).toHaveLength(1); - expect(result.fetchErrors[0].error.message).toContain( - 'Revision mismatch for rule_id rule-1: expected 0, got 1' - ); - expect(result.skippedRules).toEqual([]); - }); - - it('should handle multiple rules with mixed scenarios', () => { - const mockUpgradeableRule2 = createUpgradeableRule('rule-2', 1, 2); - const mockCurrentRule2: RuleResponse = { - ...convertPrebuiltRuleAssetToRuleResponse(baseRule), - rule_id: 'rule-2', - revision: 0, - version: 1, - }; - const mockCurrentRule3: RuleResponse = { - ...convertPrebuiltRuleAssetToRuleResponse(baseRule), - rule_id: 'rule-3', - revision: 1, - version: 1, - }; - - const result = getUpgradeableRules({ - rawUpgradeableRules: [ - mockUpgradeableRule, - mockUpgradeableRule2, - createUpgradeableRule('rule-3', 1, 2), - ], - currentRules: [mockCurrentRule, mockCurrentRule2, mockCurrentRule3], - versionSpecifiers: [ - mockVersionSpecifier, - { ...mockVersionSpecifier, rule_id: 'rule-2' }, - { ...mockVersionSpecifier, rule_id: 'rule-3', revision: 0 }, - { ...mockVersionSpecifier, rule_id: 'rule-4' }, - { ...mockVersionSpecifier, rule_id: 'rule-5', revision: 1 }, - ], - mode: ModeEnum.SPECIFIC_RULES, - }); - - expect(result.upgradeableRules).toEqual([mockUpgradeableRule, mockUpgradeableRule2]); - expect(result.fetchErrors).toHaveLength(3); - expect(result.fetchErrors[0].error.message).toContain( - 'Revision mismatch for rule_id rule-3: expected 1, got 0' - ); - expect(result.fetchErrors[1].error.message).toContain( - 'Rule with rule_id "rule-4" and version "1" not found' - ); - expect(result.fetchErrors[2].error.message).toContain( - 'Rule with rule_id "rule-5" and version "1" not found' - ); - expect(result.skippedRules).toEqual([]); - }); - }); -}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts deleted file mode 100644 index 750561b9858a9..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { withSecuritySpanSync } from '../../../../../utils/with_security_span'; -import type { - RuleResponse, - RuleUpgradeSpecifier, - SkippedRuleUpgrade, -} from '../../../../../../common/api/detection_engine'; -import { ModeEnum, SkipRuleUpgradeReasonEnum } from '../../../../../../common/api/detection_engine'; -import type { PromisePoolError } from '../../../../../utils/promise_pool'; -import type { Mode } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; - -export const getUpgradeableRules = ({ - rawUpgradeableRules, - currentRules, - versionSpecifiers, - mode, -}: { - rawUpgradeableRules: RuleTriad[]; - currentRules: RuleResponse[]; - versionSpecifiers?: RuleUpgradeSpecifier[]; - mode: Mode; -}) => { - return withSecuritySpanSync(getUpgradeableRules.name, () => { - const upgradeableRules = new Map( - rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) - ); - const fetchErrors: Array> = []; - const skippedRules: SkippedRuleUpgrade[] = []; - - if (mode === ModeEnum.SPECIFIC_RULES) { - const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); - const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); - versionSpecifiers?.forEach((rule) => { - // Check that the requested rule was found - if (!installedRuleIds.has(rule.rule_id)) { - fetchErrors.push({ - error: new Error( - `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` - ), - item: rule, - }); - return; - } - - // Check that the requested rule is upgradeable - if (!upgradeableRuleIds.has(rule.rule_id)) { - skippedRules.push({ - rule_id: rule.rule_id, - reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, - }); - return; - } - - // Check that rule revisions match (no update slipped in since the user reviewed the list) - const currentRevision = currentRules.find( - (currentRule) => currentRule.rule_id === rule.rule_id - )?.revision; - if (rule.revision !== currentRevision) { - fetchErrors.push({ - error: new Error( - `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` - ), - item: rule, - }); - // Remove the rule from the list of upgradeable rules - if (upgradeableRules.has(rule.rule_id)) { - upgradeableRules.delete(rule.rule_id); - } - } - }); - } - - return { - upgradeableRules: Array.from(upgradeableRules.values()), - fetchErrors, - skippedRules, - }; - }); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts new file mode 100644 index 0000000000000..6bea2c71123da --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts @@ -0,0 +1,235 @@ +/* + * 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 { KibanaRequest, KibanaResponseFactory } from '@kbn/core/server'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { + PerformRuleUpgradeRequestBody, + PerformRuleUpgradeResponseBody, + SkippedRuleUpgrade, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { + ModeEnum, + PickVersionValuesEnum, + SkipRuleUpgradeReasonEnum, + UpgradeConflictResolutionEnum, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../../types'; +import { buildSiemResponse } from '../../../routes/utils'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; +import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; +import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; +import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; +import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; +import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules'; +import { createModifiedPrebuiltRuleAssets } from './create_upgradeable_rules_payload'; +import { validatePerformRuleUpgradeRequest } from './validate_perform_rule_upgrade_request'; +import type { + RuleResponse, + RuleSignatureId, + RuleVersion, +} from '../../../../../../common/api/detection_engine'; +import type { PromisePoolError } from '../../../../../utils/promise_pool'; +import { zipRuleVersions } from '../../logic/rule_versions/zip_rule_versions'; +import { calculateRuleDiff } from '../../logic/diff/calculate_rule_diff'; +import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; + +export const performRuleUpgradeHandler = async ( + context: SecuritySolutionRequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory +) => { + const siemResponse = buildSiemResponse(response); + + try { + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); + const soClient = ctx.core.savedObjects.client; + const rulesClient = await ctx.alerting.getRulesClient(); + const detectionRulesClient = ctx.securitySolution.getDetectionRulesClient(); + const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); + const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + + const { isRulesCustomizationEnabled } = detectionRulesClient.getRuleCustomizationStatus(); + const defaultPickVersion = isRulesCustomizationEnabled + ? PickVersionValuesEnum.MERGED + : PickVersionValuesEnum.TARGET; + + validatePerformRuleUpgradeRequest({ + isRulesCustomizationEnabled, + payload: request.body, + defaultPickVersion, + }); + + const { mode, dry_run: isDryRun, on_conflict: onConflict } = request.body; + + const filter = mode === ModeEnum.ALL_RULES ? request.body.filter : undefined; + + const skippedRules: SkippedRuleUpgrade[] = []; + const updatedRules: RuleResponse[] = []; + const ruleErrors: Array> = []; + const allErrors: PerformRuleUpgradeResponseBody['errors'] = []; + + const ruleUpgradeQueue: Array<{ + rule_id: RuleSignatureId; + version: RuleVersion; + revision?: number; + }> = []; + + if (mode === ModeEnum.ALL_RULES) { + const allLatestVersions = await ruleAssetsClient.fetchLatestVersions(); + const latestVersionsMap = new Map( + allLatestVersions.map((version) => [version.rule_id, version]) + ); + const allCurrentVersions = await ruleObjectsClient.fetchInstalledRuleVersions({ + filter, + }); + + allCurrentVersions.forEach((current) => { + const latest = latestVersionsMap.get(current.rule_id); + if (latest && latest.version > current.version) { + ruleUpgradeQueue.push({ + rule_id: current.rule_id, + version: latest.version, + }); + } + }); + } else if (mode === ModeEnum.SPECIFIC_RULES) { + ruleUpgradeQueue.push(...request.body.rules); + } + + const BATCH_SIZE = 100; + while (ruleUpgradeQueue.length > 0) { + const targetRulesForUpgrade = ruleUpgradeQueue.splice(0, BATCH_SIZE); + + const [currentRules, latestRules] = await Promise.all([ + ruleObjectsClient.fetchInstalledRulesByIds({ + ruleIds: targetRulesForUpgrade.map(({ rule_id: ruleId }) => ruleId), + }), + ruleAssetsClient.fetchAssetsByVersion(targetRulesForUpgrade), + ]); + const baseRules = await ruleAssetsClient.fetchAssetsByVersion(currentRules); + const ruleVersionsMap = zipRuleVersions(currentRules, baseRules, latestRules); + + const upgradeableRules: RuleTriad[] = []; + targetRulesForUpgrade.forEach((targetRule) => { + const ruleVersions = ruleVersionsMap.get(targetRule.rule_id); + + const currentVersion = ruleVersions?.current; + const baseVersion = ruleVersions?.base; + const targetVersion = ruleVersions?.target; + + // Check that the requested rule was found + if (!currentVersion) { + ruleErrors.push({ + error: new Error( + `Rule with rule_id "${targetRule.rule_id}" and version "${targetRule.version}" not found` + ), + item: targetRule, + }); + return; + } + + // Check that the requested rule is upgradeable + if (!targetVersion || targetVersion.version <= currentVersion.version) { + skippedRules.push({ + rule_id: targetRule.rule_id, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, + }); + return; + } + + // Check that rule revisions match (no update slipped in since the user reviewed the list) + if (targetRule.revision != null && targetRule.revision !== currentVersion.revision) { + ruleErrors.push({ + error: new Error( + `Revision mismatch for rule_id ${targetRule.rule_id}: expected ${currentVersion.revision}, got ${targetRule.revision}` + ), + item: targetRule, + }); + return; + } + + // Check there's no conflicts + if (onConflict === UpgradeConflictResolutionEnum.SKIP) { + const ruleDiff = calculateRuleDiff(ruleVersions); + const hasConflict = ruleDiff.ruleDiff.num_fields_with_conflicts > 0; + if (hasConflict) { + skippedRules.push({ + rule_id: targetRule.rule_id, + reason: SkipRuleUpgradeReasonEnum.CONFLICT, + }); + return; + } + } + + // All checks passed, add to the list of rules to upgrade + upgradeableRules.push({ + current: currentVersion, + base: baseVersion, + target: targetVersion, + }); + }); + + const { modifiedPrebuiltRuleAssets, processingErrors } = createModifiedPrebuiltRuleAssets({ + upgradeableRules, + requestBody: request.body, + defaultPickVersion, + }); + ruleErrors.push(...processingErrors); + + if (isDryRun) { + updatedRules.push( + ...modifiedPrebuiltRuleAssets.map((rule) => convertPrebuiltRuleAssetToRuleResponse(rule)) + ); + } else { + const { results: upgradeResults, errors: installationErrors } = await upgradePrebuiltRules( + detectionRulesClient, + modifiedPrebuiltRuleAssets + ); + ruleErrors.push(...installationErrors); + updatedRules.push(...upgradeResults.map(({ result }) => result)); + } + } + + allErrors.push(...aggregatePrebuiltRuleErrors(ruleErrors)); + + if (!isDryRun) { + const { error: timelineInstallationError } = await performTimelinesInstallation( + ctx.securitySolution + ); + + if (timelineInstallationError) { + allErrors.push({ + message: timelineInstallationError, + rules: [], + }); + } + } + + const body: PerformRuleUpgradeResponseBody = { + summary: { + total: updatedRules.length + skippedRules.length + ruleErrors.length, + skipped: skippedRules.length, + succeeded: updatedRules.length, + failed: ruleErrors.length, + }, + results: { + updated: updatedRules, + skipped: skippedRules, + }, + errors: allErrors, + }; + + return response.ok({ body }); + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index 2c10d62d570db..4ab73ab2e8e64 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -5,32 +5,18 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { PERFORM_RULE_UPGRADE_URL, PerformRuleUpgradeRequestBody, - ModeEnum, - PickVersionValuesEnum, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { PerformRuleUpgradeResponseBody } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { buildSiemResponse } from '../../../routes/utils'; -import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; -import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; -import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; -import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; -import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules'; -import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; +import { routeLimitedConcurrencyTag } from '../../../../../utils/route_limited_concurrency_tag'; import { - PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, PREBUILT_RULES_OPERATION_CONCURRENCY, + PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, } from '../../constants'; -import { getUpgradeableRules } from './get_upgradeable_rules'; -import { createModifiedPrebuiltRuleAssets } from './create_upgradeable_rules_payload'; -import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; -import { validatePerformRuleUpgradeRequest } from './validate_perform_rule_upgrade_request'; -import { routeLimitedConcurrencyTag } from '../../../../../utils/route_limited_concurrency_tag'; +import { performRuleUpgradeHandler } from './perform_rule_upgrade_handler'; export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -58,93 +44,6 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => }, }, }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - - try { - const ctx = await context.resolve(['core', 'alerting', 'securitySolution', 'licensing']); - const soClient = ctx.core.savedObjects.client; - const rulesClient = await ctx.alerting.getRulesClient(); - const detectionRulesClient = ctx.securitySolution.getDetectionRulesClient(); - const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); - const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - - const { isRulesCustomizationEnabled } = detectionRulesClient.getRuleCustomizationStatus(); - const defaultPickVersion = isRulesCustomizationEnabled - ? PickVersionValuesEnum.MERGED - : PickVersionValuesEnum.TARGET; - - validatePerformRuleUpgradeRequest({ - isRulesCustomizationEnabled, - payload: request.body, - defaultPickVersion, - }); - - const { mode } = request.body; - - const versionSpecifiers = mode === ModeEnum.ALL_RULES ? undefined : request.body.rules; - const ruleTriadsMap = await fetchRuleVersionsTriad({ - ruleAssetsClient, - ruleObjectsClient, - versionSpecifiers, - }); - const ruleGroups = getRuleGroups(ruleTriadsMap); - - const { upgradeableRules, skippedRules, fetchErrors } = getUpgradeableRules({ - rawUpgradeableRules: ruleGroups.upgradeableRules, - currentRules: ruleGroups.currentRules, - versionSpecifiers, - mode, - }); - - const { modifiedPrebuiltRuleAssets, processingErrors } = createModifiedPrebuiltRuleAssets( - { - upgradeableRules, - requestBody: request.body, - defaultPickVersion, - } - ); - - const { results: updatedRules, errors: installationErrors } = await upgradePrebuiltRules( - detectionRulesClient, - modifiedPrebuiltRuleAssets - ); - const ruleErrors = [...fetchErrors, ...processingErrors, ...installationErrors]; - - const { error: timelineInstallationError } = await performTimelinesInstallation( - ctx.securitySolution - ); - - const allErrors = aggregatePrebuiltRuleErrors(ruleErrors); - if (timelineInstallationError) { - allErrors.push({ - message: timelineInstallationError, - rules: [], - }); - } - - const body: PerformRuleUpgradeResponseBody = { - summary: { - total: updatedRules.length + skippedRules.length + ruleErrors.length, - skipped: skippedRules.length, - succeeded: updatedRules.length, - failed: ruleErrors.length, - }, - results: { - updated: updatedRules.map(({ result }) => result), - skipped: skippedRules, - }, - errors: allErrors, - }; - - return response.ok({ body }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); - } - } + performRuleUpgradeHandler ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts index 4d35b98718a98..0bf1ee5cfc80a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts @@ -16,9 +16,7 @@ import { buildSiemResponse } from '../../../routes/utils'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; -import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; -import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; export const reviewRuleInstallationHandler = async ( context: SecuritySolutionRequestHandlerContext, @@ -34,15 +32,21 @@ export const reviewRuleInstallationHandler = async ( const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - const ruleVersionsMap = await fetchRuleVersionsTriad({ - ruleAssetsClient, - ruleObjectsClient, + const allLatestVersions = await ruleAssetsClient.fetchLatestVersions(); + const currentRuleVersions = await ruleObjectsClient.fetchInstalledRuleVersions(); + const currentRuleVersionsMap = new Map( + currentRuleVersions.map((version) => [version.rule_id, version]) + ); + + const installableRules = allLatestVersions.filter((latestVersion) => { + const currentVersion = currentRuleVersionsMap.get(latestVersion.rule_id); + return !currentVersion; }); - const { installableRules } = getRuleGroups(ruleVersionsMap); + const installableRuleAssets = await ruleAssetsClient.fetchAssetsByVersion(installableRules); const body: ReviewRuleInstallationResponseBody = { - stats: calculateRuleStats(installableRules), - rules: installableRules.map((prebuiltRuleAsset) => + stats: calculateRuleStats(installableRuleAssets), + rules: installableRuleAssets.map((prebuiltRuleAsset) => convertPrebuiltRuleAssetToRuleResponse(prebuiltRuleAsset) ), }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/calculate_rule_upgrade_info.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/calculate_rule_upgrade_info.ts new file mode 100644 index 0000000000000..1bfd9f09c43f7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/calculate_rule_upgrade_info.ts @@ -0,0 +1,59 @@ +/* + * 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 { pickBy } from 'lodash'; +import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema'; +import type { + RuleUpgradeInfoForReview, + ThreeWayDiff, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { ThreeWayDiffOutcome } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { invariant } from '../../../../../../common/utils/invariant'; +import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; +import type { CalculateRuleDiffResult } from '../../logic/diff/calculate_rule_diff'; + +export const calculateRuleUpgradeInfo = ( + results: CalculateRuleDiffResult[] +): RuleUpgradeInfoForReview[] => { + return results.map((result) => { + const { ruleDiff, ruleVersions } = result; + const installedCurrentVersion = ruleVersions.input.current; + const targetVersion = ruleVersions.input.target; + invariant(installedCurrentVersion != null, 'installedCurrentVersion not found'); + invariant(targetVersion != null, 'targetVersion not found'); + + const targetRule: RuleResponse = { + ...convertPrebuiltRuleAssetToRuleResponse(targetVersion), + id: installedCurrentVersion.id, + revision: installedCurrentVersion.revision + 1, + created_at: installedCurrentVersion.created_at, + created_by: installedCurrentVersion.created_by, + updated_at: new Date().toISOString(), + updated_by: installedCurrentVersion.updated_by, + }; + + return { + id: installedCurrentVersion.id, + rule_id: installedCurrentVersion.rule_id, + revision: installedCurrentVersion.revision, + version: installedCurrentVersion.version, + current_rule: installedCurrentVersion, + target_rule: targetRule, + diff: { + fields: pickBy>( + ruleDiff.fields, + (fieldDiff) => + fieldDiff.diff_outcome !== ThreeWayDiffOutcome.StockValueNoUpdate && + fieldDiff.diff_outcome !== ThreeWayDiffOutcome.MissingBaseNoUpdate + ), + num_fields_with_updates: ruleDiff.num_fields_with_updates, + num_fields_with_conflicts: ruleDiff.num_fields_with_conflicts, + num_fields_with_non_solvable_conflicts: ruleDiff.num_fields_with_non_solvable_conflicts, + }, + }; + }); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts index 9f4fa7ddc766e..05a52dbf0ed41 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts @@ -7,32 +7,35 @@ import type { KibanaRequest, KibanaResponseFactory } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { pickBy } from 'lodash'; -import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema'; +import type { ReviewPrebuiltRuleUpgradeFilter } from '../../../../../../common/api/detection_engine/prebuilt_rules/common/review_prebuilt_rules_upgrade_filter'; import type { + ReviewRuleUpgradeRequestBody, ReviewRuleUpgradeResponseBody, - RuleUpgradeInfoForReview, - RuleUpgradeStatsForReview, - ThreeWayDiff, + ReviewRuleUpgradeSort, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import { ThreeWayDiffOutcome } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import { invariant } from '../../../../../../common/utils/invariant'; import type { SecuritySolutionRequestHandlerContext } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; -import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; -import type { CalculateRuleDiffResult } from '../../logic/diff/calculate_rule_diff'; import { calculateRuleDiff } from '../../logic/diff/calculate_rule_diff'; +import type { IPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; +import type { IPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; -import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad'; -import { getRuleGroups } from '../../model/rule_groups/get_rule_groups'; +import type { RuleVersionSpecifier } from '../../logic/rule_versions/rule_version_specifier'; +import { zipRuleVersions } from '../../logic/rule_versions/zip_rule_versions'; +import { calculateRuleUpgradeInfo } from './calculate_rule_upgrade_info'; + +const DEFAULT_SORT: ReviewRuleUpgradeSort = { + field: 'name', + order: 'asc', +}; export const reviewRuleUpgradeHandler = async ( context: SecuritySolutionRequestHandlerContext, - request: KibanaRequest, + request: KibanaRequest, response: KibanaResponseFactory ) => { const siemResponse = buildSiemResponse(response); + const { page = 1, per_page: perPage = 20, sort = DEFAULT_SORT, filter } = request.body ?? {}; try { const ctx = await context.resolve(['core', 'alerting']); @@ -41,21 +44,26 @@ export const reviewRuleUpgradeHandler = async ( const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - const ruleVersionsMap = await fetchRuleVersionsTriad({ + const { diffResults, totalUpgradeableRules } = await calculateUpgradeableRulesDiff({ ruleAssetsClient, ruleObjectsClient, - }); - const { upgradeableRules } = getRuleGroups(ruleVersionsMap); - - const ruleDiffCalculationResults = upgradeableRules.map(({ current }) => { - const ruleVersions = ruleVersionsMap.get(current.rule_id); - invariant(ruleVersions != null, 'ruleVersions not found'); - return calculateRuleDiff(ruleVersions); + page, + perPage, + sort, + filter, }); const body: ReviewRuleUpgradeResponseBody = { - stats: calculateRuleStats(ruleDiffCalculationResults), - rules: calculateRuleInfos(ruleDiffCalculationResults), + stats: { + num_rules_to_upgrade_total: 0, + num_rules_with_conflicts: 0, + num_rules_with_non_solvable_conflicts: 0, + tags: [], + }, + rules: calculateRuleUpgradeInfo(diffResults), + page, + per_page: perPage, + total: totalUpgradeableRules, }; return response.ok({ body }); @@ -67,72 +75,68 @@ export const reviewRuleUpgradeHandler = async ( }); } }; -const calculateRuleStats = (results: CalculateRuleDiffResult[]): RuleUpgradeStatsForReview => { - const allTags = new Set(); - const stats = results.reduce( - (acc, result) => { - acc.num_rules_to_upgrade_total += 1; +interface CalculateUpgradeableRulesDiffArgs { + ruleAssetsClient: IPrebuiltRuleAssetsClient; + ruleObjectsClient: IPrebuiltRuleObjectsClient; + page: number; + perPage: number; + sort: ReviewRuleUpgradeSort; + filter: ReviewPrebuiltRuleUpgradeFilter | undefined; +} - if (result.ruleDiff.num_fields_with_conflicts > 0) { - acc.num_rules_with_conflicts += 1; - } +async function calculateUpgradeableRulesDiff({ + ruleAssetsClient, + ruleObjectsClient, + page, + perPage, + sort, + filter, +}: CalculateUpgradeableRulesDiffArgs) { + const allLatestVersions = await ruleAssetsClient.fetchLatestVersions(); + const latestVersionsMap = new Map(allLatestVersions.map((version) => [version.rule_id, version])); - if (result.ruleDiff.num_fields_with_non_solvable_conflicts > 0) { - acc.num_rules_with_non_solvable_conflicts += 1; - } + const currentRuleVersions = filter?.rule_ids + ? await ruleObjectsClient.fetchInstalledRuleVersionsByIds({ + ruleIds: filter.rule_ids, + sortField: sort.field, + sortOrder: sort.order, + }) + : await ruleObjectsClient.fetchInstalledRuleVersions({ + filter, + sortField: sort.field, + sortOrder: sort.order, + }); + const upgradeableRuleIds = currentRuleVersions + .filter((rule) => { + const targetVersion = latestVersionsMap.get(rule.rule_id); + return targetVersion != null && rule.version < targetVersion.version; + }) + .map((rule) => rule.rule_id); + const totalUpgradeableRules = upgradeableRuleIds.length; - result.ruleVersions.input.current?.tags.forEach((tag) => allTags.add(tag)); - - return acc; - }, - { - num_rules_to_upgrade_total: 0, - num_rules_with_conflicts: 0, - num_rules_with_non_solvable_conflicts: 0, - } + const pagedRuleIds = upgradeableRuleIds.slice((page - 1) * perPage, page * perPage); + const currentRules = await ruleObjectsClient.fetchInstalledRulesByIds({ + ruleIds: pagedRuleIds, + sortField: sort.field, + sortOrder: sort.order, + }); + const latestRules = await ruleAssetsClient.fetchAssetsByVersion( + currentRules.map(({ rule_id: ruleId }) => latestVersionsMap.get(ruleId) as RuleVersionSpecifier) ); + const baseRules = await ruleAssetsClient.fetchAssetsByVersion(currentRules); + const ruleVersionsMap = zipRuleVersions(currentRules, baseRules, latestRules); + + // Calculate the diff between current, base, and target versions + // Iterate through the current rules array to keep the order of the results + const diffResults = currentRules.map((current) => { + const base = ruleVersionsMap.get(current.rule_id)?.base; + const target = ruleVersionsMap.get(current.rule_id)?.target; + return calculateRuleDiff({ current, base, target }); + }); return { - ...stats, - tags: Array.from(allTags), + diffResults, + totalUpgradeableRules, }; -}; -const calculateRuleInfos = (results: CalculateRuleDiffResult[]): RuleUpgradeInfoForReview[] => { - return results.map((result) => { - const { ruleDiff, ruleVersions } = result; - const installedCurrentVersion = ruleVersions.input.current; - const targetVersion = ruleVersions.input.target; - invariant(installedCurrentVersion != null, 'installedCurrentVersion not found'); - invariant(targetVersion != null, 'targetVersion not found'); - - const targetRule: RuleResponse = { - ...convertPrebuiltRuleAssetToRuleResponse(targetVersion), - id: installedCurrentVersion.id, - revision: installedCurrentVersion.revision + 1, - created_at: installedCurrentVersion.created_at, - created_by: installedCurrentVersion.created_by, - updated_at: new Date().toISOString(), - updated_by: installedCurrentVersion.updated_by, - }; - - return { - id: installedCurrentVersion.id, - rule_id: installedCurrentVersion.rule_id, - revision: installedCurrentVersion.revision, - current_rule: installedCurrentVersion, - target_rule: targetRule, - diff: { - fields: pickBy>( - ruleDiff.fields, - (fieldDiff) => - fieldDiff.diff_outcome !== ThreeWayDiffOutcome.StockValueNoUpdate && - fieldDiff.diff_outcome !== ThreeWayDiffOutcome.MissingBaseNoUpdate - ), - num_fields_with_updates: ruleDiff.num_fields_with_updates, - num_fields_with_conflicts: ruleDiff.num_fields_with_conflicts, - num_fields_with_non_solvable_conflicts: ruleDiff.num_fields_with_non_solvable_conflicts, - }, - }; - }); -}; +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts index 54f51752b7ab8..24b3865fc5ea0 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts @@ -5,12 +5,16 @@ * 2.0. */ -import { REVIEW_RULE_UPGRADE_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + REVIEW_RULE_UPGRADE_URL, + ReviewRuleUpgradeRequestBody, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { routeLimitedConcurrencyTag } from '../../../../../utils/route_limited_concurrency_tag'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { - PREBUILT_RULES_OPERATION_CONCURRENCY, PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, + PREBUILT_RULES_UPGRADE_REVIEW_CONCURRENCY, } from '../../constants'; import { reviewRuleUpgradeHandler } from './review_rule_upgrade_handler'; @@ -25,7 +29,7 @@ export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => }, }, options: { - tags: [routeLimitedConcurrencyTag(PREBUILT_RULES_OPERATION_CONCURRENCY)], + tags: [routeLimitedConcurrencyTag(PREBUILT_RULES_UPGRADE_REVIEW_CONCURRENCY)], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, @@ -34,7 +38,11 @@ export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => .addVersion( { version: '1', - validate: {}, + validate: { + request: { + body: buildRouteValidationWithZod(ReviewRuleUpgradeRequestBody), + }, + }, }, reviewRuleUpgradeHandler ); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/constants.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/constants.ts index 7d281ae96e321..86fd9b45025dc 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/constants.ts @@ -10,3 +10,11 @@ export const PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS = 1_800_000 as const; // // Only one rule installation or upgrade request can be processed at a time. // Multiple requests can lead to high memory usage and unexpected behavior. export const PREBUILT_RULES_OPERATION_CONCURRENCY = 1; + +/** + * Prebuilt rules upgrade review API endpoint max concurrency. + * + * It differs from PREBUILT_RULES_OPERATION_CONCURRENCY since upgrade review API endpoint + * is expected to be requested much more often than the other prebuilt rules API endpoints. + */ +export const PREBUILT_RULES_UPGRADE_REVIEW_CONCURRENCY = 3; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts index 1138a48cc39d4..2bf7cf5c44364 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts @@ -9,36 +9,73 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { RuleResponse, RuleSignatureId, + RuleTagArray, } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { withSecuritySpan } from '../../../../../utils/with_security_span'; import { findRules } from '../../../rule_management/logic/search/find_rules'; -import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; import { internalRuleToAPIResponse } from '../../../rule_management/logic/detection_rules_client/converters/internal_rule_to_api_response'; +import { convertRulesFilterToKQL } from '../../../../../../common/detection_engine/rule_management/rule_filtering'; +import type { + FindRulesSortField, + PrebuiltRulesFilter, + SortOrder, +} from '../../../../../../common/api/detection_engine'; +import { MAX_PREBUILT_RULES_COUNT } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; +import type { RuleVersionSpecifier } from '../rule_versions/rule_version_specifier'; + +interface FetchAllInstalledRulesArgs { + page?: number; + perPage?: number; + filter?: PrebuiltRulesFilter; + sortField?: FindRulesSortField; + sortOrder?: SortOrder; +} + +interface FetchAllInstalledRuleVersionsArgs { + filter?: PrebuiltRulesFilter; + sortField?: FindRulesSortField; + sortOrder?: SortOrder; +} + +interface FetchInstalledRuleVersionsByIdsArgs { + ruleIds: RuleSignatureId[]; + sortField?: FindRulesSortField; + sortOrder?: SortOrder; +} + +interface FetchInstalledRulesByIdsArgs { + ruleIds: RuleSignatureId[]; + sortField?: FindRulesSortField; + sortOrder?: SortOrder; +} export interface IPrebuiltRuleObjectsClient { - fetchAllInstalledRules(): Promise; - fetchInstalledRulesByIds(ruleIds: string[]): Promise; + fetchInstalledRulesByIds(args: FetchInstalledRulesByIdsArgs): Promise; + fetchInstalledRules(args?: FetchAllInstalledRulesArgs): Promise; + fetchInstalledRuleVersionsByIds( + args: FetchInstalledRuleVersionsByIdsArgs + ): Promise>; + fetchInstalledRuleVersions( + args?: FetchAllInstalledRuleVersionsArgs + ): Promise>; } export const createPrebuiltRuleObjectsClient = ( rulesClient: RulesClient ): IPrebuiltRuleObjectsClient => { return { - fetchAllInstalledRules: (): Promise => { - return withSecuritySpan('IPrebuiltRuleObjectsClient.fetchInstalledRules', async () => { - const rulesData = await getExistingPrepackagedRules({ rulesClient }); - const rules = rulesData.map((rule) => internalRuleToAPIResponse(rule)); - return rules; - }); - }, - fetchInstalledRulesByIds: (ruleIds: RuleSignatureId[]): Promise => { + fetchInstalledRulesByIds: ({ ruleIds, sortField = 'createdAt', sortOrder = 'desc' }) => { return withSecuritySpan('IPrebuiltRuleObjectsClient.fetchInstalledRulesByIds', async () => { + if (ruleIds.length === 0) { + return []; + } + const { data } = await findRules({ rulesClient, perPage: ruleIds.length, page: 1, - sortField: 'createdAt', - sortOrder: 'desc', + sortField, + sortOrder, fields: undefined, filter: `alert.attributes.params.ruleId:(${ruleIds.join(' or ')})`, }); @@ -47,5 +84,78 @@ export const createPrebuiltRuleObjectsClient = ( return rules; }); }, + fetchInstalledRules: ({ page, perPage, sortField, sortOrder, filter } = {}) => { + return withSecuritySpan('IPrebuiltRuleObjectsClient.fetchInstalledRules', async () => { + const filterKQL = convertRulesFilterToKQL({ + showElasticRules: true, + filter: filter?.name, + tags: filter?.tags, + customizationStatus: filter?.customization_status, + }); + + const rulesData = await findRules({ + rulesClient, + filter: filterKQL, + perPage, + page, + sortField, + sortOrder, + fields: undefined, + }); + const rules = rulesData.data.map((rule) => internalRuleToAPIResponse(rule)); + return rules; + }); + }, + fetchInstalledRuleVersionsByIds: ({ ruleIds, sortField, sortOrder }) => { + return withSecuritySpan( + 'IPrebuiltRuleObjectsClient.fetchInstalledRuleVersionsByIds', + async () => { + const filterKQL = convertRulesFilterToKQL({ + showElasticRules: true, + }); + + const rulesData = await findRules({ + rulesClient, + ruleIds, + filter: filterKQL, + perPage: MAX_PREBUILT_RULES_COUNT, + page: 1, + sortField, + sortOrder, + fields: ['params.ruleId', 'params.version', 'tags'], + }); + return rulesData.data.map((rule) => ({ + rule_id: rule.params.ruleId, + version: rule.params.version, + tags: rule.tags, + })); + } + ); + }, + fetchInstalledRuleVersions: ({ filter, sortField, sortOrder } = {}) => { + return withSecuritySpan('IPrebuiltRuleObjectsClient.fetchInstalledRuleVersions', async () => { + const filterKQL = convertRulesFilterToKQL({ + showElasticRules: true, + filter: filter?.name, + tags: filter?.tags, + customizationStatus: filter?.customization_status, + }); + + const rulesData = await findRules({ + rulesClient, + filter: filterKQL, + perPage: MAX_PREBUILT_RULES_COUNT, + page: 1, + sortField, + sortOrder, + fields: ['params.ruleId', 'params.version', 'tags'], + }); + return rulesData.data.map((rule) => ({ + rule_id: rule.params.ruleId, + version: rule.params.version, + tags: rule.tags, + })); + }); + }, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts index 11a5660e77a31..d4cd56ae19f32 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { PrebuiltRulesFilter } from '../../../../../../common/api/detection_engine'; +import { MAX_PREBUILT_RULES_COUNT } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; import type { RuleVersions } from '../diff/calculate_rule_diff'; import type { IPrebuiltRuleAssetsClient } from '../rule_assets/prebuilt_rule_assets_client'; import type { IPrebuiltRuleObjectsClient } from '../rule_objects/prebuilt_rule_objects_client'; @@ -15,19 +17,25 @@ interface GetRuleVersionsMapArgs { ruleObjectsClient: IPrebuiltRuleObjectsClient; ruleAssetsClient: IPrebuiltRuleAssetsClient; versionSpecifiers?: RuleVersionSpecifier[]; + filter?: PrebuiltRulesFilter; } export async function fetchRuleVersionsTriad({ ruleObjectsClient, ruleAssetsClient, versionSpecifiers, + filter, }: GetRuleVersionsMapArgs): Promise> { const [currentRules, latestRules] = await Promise.all([ versionSpecifiers - ? ruleObjectsClient.fetchInstalledRulesByIds( - versionSpecifiers.map(({ rule_id: ruleId }) => ruleId) - ) - : ruleObjectsClient.fetchAllInstalledRules(), + ? ruleObjectsClient.fetchInstalledRulesByIds({ + ruleIds: versionSpecifiers.map(({ rule_id: ruleId }) => ruleId), + }) + : ruleObjectsClient.fetchInstalledRules({ + filter, + page: 1, + perPage: MAX_PREBUILT_RULES_COUNT, + }), versionSpecifiers ? ruleAssetsClient.fetchAssetsByVersion(versionSpecifiers) : ruleAssetsClient.fetchLatestAssets(), diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts index 24b2954547e40..9a7eb8fdf6f56 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts @@ -49,16 +49,20 @@ export const getRulesCount = async ({ export const getRules = async ({ rulesClient, filter, + page = 1, + perPage = MAX_PREBUILT_RULES_COUNT, }: { rulesClient: RulesClient; filter: string; + page?: number; + perPage?: number; }): Promise => withSecuritySpan('getRules', async () => { const rules = await findRules({ rulesClient, filter, - perPage: MAX_PREBUILT_RULES_COUNT, - page: 1, + perPage, + page, sortField: 'createdAt', sortOrder: 'desc', fields: undefined, @@ -80,11 +84,17 @@ export const getNonPackagedRules = async ({ export const getExistingPrepackagedRules = async ({ rulesClient, + page, + perPage, }: { rulesClient: RulesClient; + page?: number; + perPage?: number; }): Promise => { return getRules({ rulesClient, + page, + perPage, filter: KQL_FILTER_IMMUTABLE_RULES, }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/preview_prebuilt_rules_upgrade.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/preview_prebuilt_rules_upgrade.ts index 9d80d14257c37..d5fbc330ee3e2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/preview_prebuilt_rules_upgrade.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/preview_prebuilt_rules_upgrade.ts @@ -11,7 +11,6 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, fetchFirstPrebuiltRuleUpgradeReviewDiff, - reviewPrebuiltRulesToUpgrade, } from '../../../../utils'; import { setUpRuleUpgrade } from '../../../../utils/rules/prebuilt_rules/set_up_rule_upgrade'; @@ -36,241 +35,6 @@ export default ({ getService }: FtrProviderContext): void => { describe( withHistoricalVersions ? 'with historical versions' : 'without historical versions', () => { - describe('stats', () => { - it('returns num of rules with upgrades', async () => { - await setUpRuleUpgrade({ - assets: [ - { - installed: { - rule_id: 'query-rule', - type: 'query', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'query-rule', - type: 'query', - version: 2, - }, - }, - { - installed: { - rule_id: 'saved-query-rule', - type: 'query', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'saved-query-rule', - type: 'query', - version: 2, - }, - }, - ], - removeInstalledAssets: !withHistoricalVersions, - deps, - }); - - const response = await reviewPrebuiltRulesToUpgrade(supertest); - - expect(response.stats).toMatchObject({ - num_rules_to_upgrade_total: 2, - }); - }); - - it('returns zero conflicts when there are no conflicts', async () => { - await setUpRuleUpgrade({ - assets: [ - { - installed: { - rule_id: 'query-rule', - type: 'query', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'query-rule', - type: 'query', - version: 2, - }, - }, - { - installed: { - rule_id: 'saved-query-rule', - type: 'query', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'saved-query-rule', - type: 'query', - version: 2, - }, - }, - ], - removeInstalledAssets: !withHistoricalVersions, - deps, - }); - - const response = await reviewPrebuiltRulesToUpgrade(supertest); - - expect(response.stats).toMatchObject({ - num_rules_with_conflicts: 0, - num_rules_with_non_solvable_conflicts: 0, - }); - }); - - it('returns num of rules with conflicts', async () => { - await setUpRuleUpgrade({ - assets: [ - { - installed: { - rule_id: 'query-rule', - type: 'query', - name: 'Initial name', - version: 1, - }, - patch: { - rule_id: 'query-rule', - name: 'Customized name', - }, - upgrade: { - rule_id: 'query-rule', - type: 'query', - name: 'Updated name', - version: 2, - }, - }, - { - installed: { - rule_id: 'saved-query-rule', - type: 'query', - tags: ['tagA'], - version: 1, - }, - patch: { - rule_id: 'saved-query-rule', - tags: ['tagB'], - }, - upgrade: { - rule_id: 'saved-query-rule', - type: 'query', - tags: ['tagC'], - version: 2, - }, - }, - ], - removeInstalledAssets: !withHistoricalVersions, - deps, - }); - - const response = await reviewPrebuiltRulesToUpgrade(supertest); - - expect(response.stats).toMatchObject({ - num_rules_with_conflicts: 2, - }); - }); - - it('returns num of rules with non-solvable conflicts', async () => { - await setUpRuleUpgrade({ - assets: [ - // Name field has a non-solvable upgrade conflict - { - installed: { - rule_id: 'query-rule', - type: 'query', - name: 'Initial name', - version: 1, - }, - patch: { - rule_id: 'query-rule', - name: 'Customized name', - }, - upgrade: { - rule_id: 'query-rule', - type: 'query', - name: 'Updated name', - version: 2, - }, - }, - // tags field values are merged resulting in a solvable upgrade conflict - { - installed: { - rule_id: 'saved-query-rule', - type: 'query', - tags: ['tagA'], - version: 1, - }, - patch: { - rule_id: 'saved-query-rule', - tags: ['tagB'], - }, - upgrade: { - rule_id: 'saved-query-rule', - type: 'query', - tags: ['tagC'], - version: 2, - }, - }, - ], - removeInstalledAssets: !withHistoricalVersions, - deps, - }); - - const response = await reviewPrebuiltRulesToUpgrade(supertest); - - expect(response.stats).toMatchObject({ - // Missing rule's base version doesn't allow to detect non solvable conflicts - num_rules_with_non_solvable_conflicts: withHistoricalVersions ? 1 : 0, - }); - }); - - if (!withHistoricalVersions) { - it('returns num of rules with conflicts caused by missing historical versions', async () => { - await setUpRuleUpgrade({ - assets: [ - { - installed: { - rule_id: 'query-rule', - type: 'query', - name: 'Initial name', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'query-rule', - type: 'query', - version: 2, - }, - }, - { - installed: { - rule_id: 'saved-query-rule', - type: 'query', - version: 1, - }, - patch: {}, - upgrade: { - rule_id: 'saved-query-rule', - type: 'query', - name: 'Updated name', - version: 2, - }, - }, - ], - removeInstalledAssets: true, - deps, - }); - - const response = await reviewPrebuiltRulesToUpgrade(supertest); - - expect(response.stats).toMatchObject({ - num_rules_with_conflicts: 2, - }); - }); - } - }); - describe('fields diff stats', () => { it('returns num of fields with updates', async () => { await setUpRuleUpgrade({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts index b551d793406ce..6ef7bd9bc514a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/update_prebuilt_rules_package.ts @@ -21,7 +21,6 @@ import { installPrebuiltRulesPackageByVersion, performUpgradePrebuiltRules, reviewPrebuiltRulesToInstall, - reviewPrebuiltRulesToUpgrade, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; @@ -220,13 +219,6 @@ export default ({ getService }: FtrProviderContext): void => { ) ); - // Verify that the upgrade _review endpoint returns the same number of rules to upgrade as the status endpoint - const prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation = - await reviewPrebuiltRulesToUpgrade(supertest); - expect( - prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation.stats.num_rules_to_upgrade_total - ).toBe(statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade); - // Call the upgrade _perform endpoint to upgrade all rules to their target version and verify that the number // of upgraded rules is the same as the one returned by the _review endpoint and the status endpoint const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = @@ -238,9 +230,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade ); - expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( - prebuiltRulesToUpgradeReviewAfterLatestPackageInstallation.stats.num_rules_to_upgrade_total - ); // Get installed rules diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts index f0f5c9c42a8e8..a8902a3513392 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + deleteAlertsAndRules, + deletePrebuiltRulesAssets, +} from '../../../../tasks/api_calls/common'; import { createRuleAssetSavedObject } from '../../../../helpers/rules'; import { getInstallSingleRuleButtonByRuleId, @@ -42,6 +46,8 @@ describe( { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { beforeEach(() => { + deletePrebuiltRulesAssets(); + deleteAlertsAndRules(); preventPrebuiltRulesPackageInstallation(); login(); visitRulesManagementTable(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_customized_rules.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_customized_rules.cy.ts index 36c9d2f851e3b..515ec4e633127 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_customized_rules.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_customized_rules.cy.ts @@ -114,10 +114,6 @@ describe( selectRulesByName(['Old rule 1', 'Old rule 2']); cy.get(UPGRADE_SELECTED_RULES_BUTTON).should('be.disabled'); }); - - it('should disable `Update all rules` button when all rules have conflicts', () => { - cy.get(UPGRADE_ALL_RULES_BUTTON).should('be.disabled'); - }); }); describe('Upgrade of prebuilt rules with and without conflicts', () => { @@ -323,10 +319,6 @@ describe( ]); cy.get(UPGRADE_SELECTED_RULES_BUTTON).should('be.disabled'); }); - - it('should disable `Update all rules` button', () => { - cy.get(UPGRADE_ALL_RULES_BUTTON).should('be.disabled'); - }); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 08de9decbddf1..2a0b601b2dc60 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -145,22 +145,16 @@ export const bulkCreateRuleAssets = ({ const bulkIndexRequestBody = rules.reduce((body, rule) => { const document = JSON.stringify(rule); const documentId = `security-rule:${rule['security-rule'].rule_id}`; - const historicalDocumentId = `${documentId}_${rule['security-rule'].version}`; + const documentIdWithVersion = `${documentId}_${rule['security-rule'].version}`; - const indexRuleAsset = `${JSON.stringify({ - index: { - _index: index, - _id: documentId, - }, - })}\n${document}\n`; const indexHistoricalRuleAsset = `${JSON.stringify({ index: { _index: index, - _id: historicalDocumentId, + _id: documentIdWithVersion, }, })}\n${document}\n`; - return body.concat(indexRuleAsset, indexHistoricalRuleAsset); + return body.concat(indexHistoricalRuleAsset); }, ''); cy.task('putMapping', index); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts index 0dd833810f9be..a7f35548eb262 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts @@ -80,6 +80,15 @@ export const interceptUpgradeRequestToFail = (rules: Array 1 ? 'rules' : 'rule'; cy.get(TOASTER) .should('be.visible') - .should('have.text', `${rules.length} ${rulesString} updated successfully.`); + .should('contain', `${rules.length} ${rulesString} updated successfully.`); }; export const assertRuleUpgradeFailureToastShown = (rules: Array) => {