From a46ccff0e240bd7913e37a38e3e94a24a02f7539 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 22 Oct 2024 16:12:03 -0400 Subject: [PATCH 1/3] adds filter component and badge --- .../rule_management/logic/types.ts | 6 ++ .../upgrade_prebuilt_rules_table_context.tsx | 1 + .../upgrade_prebuilt_rules_table_filters.tsx | 50 ++++++++--- .../upgrade_rule_source_filter_popover.tsx | 90 +++++++++++++++++++ .../use_filter_prebuilt_rules_to_upgrade.ts | 28 +++++- .../components/rules_table/use_columns.tsx | 46 +++++++++- .../detection_engine/rules/translations.ts | 28 ++++++ 7 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index e12442c97aa4c..10e094fe84cf4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -99,6 +99,7 @@ export interface FilterOptions { excludeRuleTypes?: Type[]; enabled?: boolean; // undefined is to display all the rules ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all" + ruleSource?: RuleSourceTypesEnum[]; // undefined is to display all the rules } export interface FetchRulesResponse { @@ -202,3 +203,8 @@ export interface FindRulesReferencedByExceptionsProps { lists: FindRulesReferencedByExceptionsListProp[]; signal?: AbortSignal; } + +export enum RuleSourceTypesEnum { + MODIFIED = 'MODIFIED', + UNMODIFIED = 'UNMODIFIED', +} diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index aad1e053e15b6..bb197a2a02c50 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -118,6 +118,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ const [filterOptions, setFilterOptions] = useState({ filter: '', tags: [], + ruleSource: [], }); const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages(); diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx index 215a810bf3aa2..16df8799c1f4b 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx @@ -9,10 +9,13 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import type { RuleSourceTypesEnum } 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 { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; +import { RuleSourceFilterPopover } from './upgrade_rule_source_filter_popover'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; @@ -28,7 +31,11 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { actions: { setFilterOptions }, } = useUpgradePrebuiltRulesTableContext(); - const { tags: selectedTags } = filterOptions; + const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( + 'prebuiltRulesCustomizationEnabled' + ); + + const { tags: selectedTags, ruleSource: selectedRuleSource = [] } = filterOptions; const handleOnSearch = useCallback( (filterString: string) => { @@ -52,22 +59,45 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { [selectedTags, setFilterOptions] ); + const handleSelectedRuleSource = useCallback( + (newRuleSource: RuleSourceTypesEnum[]) => { + if (!isEqual(newRuleSource, selectedRuleSource)) { + setFilterOptions((filters) => ({ + ...filters, + ruleSource: newRuleSource, + })); + } + }, + [selectedRuleSource, setFilterOptions] + ); + return ( - + - - - + + {isPrebuiltRulesCustomizationEnabled && ( + + + + )} + + + + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx new file mode 100644 index 0000000000000..5ec22b5341f3a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx @@ -0,0 +1,90 @@ +/* + * 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 React, { useState, useMemo } from 'react'; +import type { EuiSelectableOption } from '@elastic/eui'; +import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui'; +import { RuleSourceTypesEnum } from '../../../../rule_management/logic'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; +import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; + +interface RuleSourceFilterPopoverProps { + selectedRuleSource: RuleSourceTypesEnum[]; + onSelectedRuleSourceChanged: (newRuleSource: RuleSourceTypesEnum[]) => void; +} + +const RULE_SOURCE_POPOVER_WIDTH = 200; + +const RuleSourceFilterPopoverComponent = ({ + selectedRuleSource, + onSelectedRuleSourceChanged, +}: RuleSourceFilterPopoverProps) => { + const [isRuleSourcePopoverOpen, setIsRuleSourcePopoverOpen] = useState(false); + + const selectableOptions: EuiSelectableOption[] = useMemo( + () => [ + { + label: i18n.MODIFIED_LABEL, + key: RuleSourceTypesEnum.MODIFIED, + checked: selectedRuleSource.includes(RuleSourceTypesEnum.MODIFIED) ? 'on' : undefined, + }, + { + label: i18n.UNMODIFIED_LABEL, + key: RuleSourceTypesEnum.UNMODIFIED, + checked: selectedRuleSource.includes(RuleSourceTypesEnum.UNMODIFIED) ? 'on' : undefined, + }, + ], + [selectedRuleSource] + ); + + const handleSelectableOptionsChange = ( + newOptions: EuiSelectableOption[], + _: unknown, + changedOption: EuiSelectableOption + ) => { + toggleSelectedGroup( + changedOption.key ?? '', + selectedRuleSource, + onSelectedRuleSourceChanged as (args: string[]) => void + ); + }; + + const triggerButton = ( + setIsRuleSourcePopoverOpen(!isRuleSourcePopoverOpen)} + numFilters={selectableOptions.length} + isSelected={isRuleSourcePopoverOpen} + hasActiveFilters={selectedRuleSource.length > 0} + numActiveFilters={selectedRuleSource.length} + data-test-subj="rule-source-filter-popover-button" + > + {i18n.RULE_SOURCE} + + ); + + return ( + setIsRuleSourcePopoverOpen(!isRuleSourcePopoverOpen)} + panelPaddingSize="none" + repositionOnScroll + panelProps={{ + 'data-test-subj': 'rule-source-filter-popover', + }} + > + + {(list) =>
{list}
} +
+
+ ); +}; + +export const RuleSourceFilterPopover = React.memo(RuleSourceFilterPopoverComponent); diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts index 342a1e6e8768e..0f45ca84ea584 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts @@ -7,9 +7,12 @@ import { useMemo } from 'react'; import type { RuleUpgradeInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { FilterOptions } from '../../../../rule_management/logic/types'; +import { RuleSourceTypesEnum, type FilterOptions } from '../../../../rule_management/logic/types'; -export type UpgradePrebuiltRulesTableFilterOptions = Pick; +export type UpgradePrebuiltRulesTableFilterOptions = Pick< + FilterOptions, + 'filter' | 'tags' | 'ruleSource' +>; export const useFilterPrebuiltRulesToUpgrade = ({ rules, @@ -19,7 +22,7 @@ export const useFilterPrebuiltRulesToUpgrade = ({ filterOptions: UpgradePrebuiltRulesTableFilterOptions; }) => { const filteredRules = useMemo(() => { - const { filter, tags } = filterOptions; + const { filter, tags, ruleSource } = filterOptions; return rules.filter((ruleInfo) => { if (filter && !ruleInfo.current_rule.name.toLowerCase().includes(filter.toLowerCase())) { return false; @@ -29,6 +32,25 @@ export const useFilterPrebuiltRulesToUpgrade = ({ return tags.every((tag) => ruleInfo.current_rule.tags.includes(tag)); } + if (ruleSource && ruleSource.length > 0) { + if ( + ruleSource.includes(RuleSourceTypesEnum.MODIFIED) && + ruleSource.includes(RuleSourceTypesEnum.UNMODIFIED) + ) { + return true; + } else if ( + ruleSource.includes(RuleSourceTypesEnum.MODIFIED) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized; + } else if ( + ruleSource.includes(RuleSourceTypesEnum.UNMODIFIED) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized === false; + } + } + return true; }); }, [filterOptions, rules]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index c38fec638e478..818058b1ae530 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -10,6 +10,7 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiToolTip } fro import { FormattedMessage } from '@kbn/i18n-react'; import moment from 'moment'; import React, { useMemo } from 'react'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { RulesTableEmptyColumnName } from './rules_table_empty_column_name'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; import { @@ -233,6 +234,33 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + {i18n.MODIFIED_LABEL} + + ); + }, + width: '90px', + truncateText: true, +}; + const useActionsColumn = ({ showExceptionsDuplicateConfirmation, showManualRuleRunConfirmation, @@ -265,6 +293,9 @@ export const useRulesColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( + 'prebuiltRulesCustomizationEnabled' + ); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -282,6 +313,7 @@ export const useRulesColumns = ({ return useMemo( () => [ ruleNameColumn, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -352,13 +384,14 @@ export const useRulesColumns = ({ ...(hasCRUDPermissions ? [actionsColumn] : []), ], [ - actionsColumn, - enabledColumn, + ruleNameColumn, + isPrebuiltRulesCustomizationEnabled, + showRelatedIntegrations, executionStatusColumn, snoozeColumn, + enabledColumn, hasCRUDPermissions, - ruleNameColumn, - showRelatedIntegrations, + actionsColumn, ] ); }; @@ -380,6 +413,9 @@ export const useMonitoringColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( + 'prebuiltRulesCustomizationEnabled' + ); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -399,6 +435,7 @@ export const useMonitoringColumns = ({ ...ruleNameColumn, width: '28%', }, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -503,6 +540,7 @@ export const useMonitoringColumns = ({ enabledColumn, executionStatusColumn, hasCRUDPermissions, + isPrebuiltRulesCustomizationEnabled, ruleNameColumn, showRelatedIntegrations, ] diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index b573edd84343f..72aa8923d4b77 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -697,6 +697,13 @@ export const COLUMN_INTEGRATIONS = i18n.translate( } ); +export const COLUMN_MODIFIED = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.columns.modifiedTitle', + { + defaultMessage: 'MODIFIED', + } +); + export const COLUMN_ENABLE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.columns.enabledTitle', { @@ -823,6 +830,27 @@ export const NO_TAGS_AVAILABLE = i18n.translate( } ); +export const RULE_SOURCE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.filters.ruleSourceLabel', + { + defaultMessage: 'Modifications', + } +); + +export const MODIFIED_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.upgradeRules.modifiedLabel', + { + defaultMessage: 'Modified', + } +); + +export const UNMODIFIED_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.upgradeRules.unmodifiedLabel', + { + defaultMessage: 'Unmodified', + } +); + export const RULE_EXECTION_STATUS_FILTER = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.filters.ruleExecutionStatusFilter', { From 343e43df68f6e1a540ad219a265639a970d6b687 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 24 Oct 2024 11:47:48 -0400 Subject: [PATCH 2/3] adds tooltip --- ...e_upgrade_prebuilt_rules_table_columns.tsx | 39 ++++++++++++++++++- .../components/rules_table/use_columns.tsx | 16 ++++---- .../detection_engine/rules/translations.ts | 7 ++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index 09009c98c2858..a24f109798e9a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -6,7 +6,14 @@ */ import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiBadge, EuiButtonEmpty, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { + EuiBadge, + EuiButtonEmpty, + EuiLink, + EuiLoadingSpinner, + EuiText, + EuiToolTip, +} from '@elastic/eui'; import React, { useMemo } from 'react'; import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade/rule_upgrade_state'; import { RulesTableEmptyColumnName } from '../rules_table_empty_column_name'; @@ -104,6 +111,35 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'current_rule.rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + + {i18n.MODIFIED_LABEL} + + + ); + }, + width: '90px', + truncateText: true, +}; + const createUpgradeButtonColumn = ( upgradeRules: UpgradePrebuiltRulesTableActions['upgradeRules'], loadingRules: RuleSignatureId[], @@ -157,6 +193,7 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { return useMemo( () => [ RULE_NAME_COLUMN, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 818058b1ae530..92ba276ac4f8e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -248,13 +248,15 @@ const MODIFIED_COLUMN: TableColumn = { } return ( - - {i18n.MODIFIED_LABEL} - + + + {i18n.MODIFIED_LABEL} + + ); }, width: '90px', diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 72aa8923d4b77..b6a3bbdc0ad90 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -851,6 +851,13 @@ export const UNMODIFIED_LABEL = i18n.translate( } ); +export const MODIFIED_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionEngine.upgradeRules.modifiedTooltipDescription', + { + defaultMessage: 'This Elastic rule has been modified.', + } +); + export const RULE_EXECTION_STATUS_FILTER = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.filters.ruleExecutionStatusFilter', { From e28e454eb0d4016cd2317700d6ac8e6647d0aa0e Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 5 Nov 2024 15:04:50 -0500 Subject: [PATCH 3/3] addresses comments and adds tests --- .../customized_prebuilt_rule_badge.tsx | 8 +- .../components/rule_details/translations.ts | 4 +- .../hooks/use_default_index_pattern.tsx | 6 +- ...s_prebuilt_rules_customization_enabled.tsx | 12 ++ .../rule_management/logic/types.ts | 8 +- .../upgrade_prebuilt_rules_table_context.tsx | 6 +- .../upgrade_prebuilt_rules_table_filters.tsx | 16 ++- ...ade_rule_customization_filter_popover.tsx} | 42 +++---- .../use_filter_prebuilt_rules_to_upgrade.ts | 10 +- .../use_prebuilt_rules_upgrade_state.ts | 6 +- ...e_upgrade_prebuilt_rules_table_columns.tsx | 5 + .../components/rules_table/use_columns.tsx | 20 ++-- .../integrations_popover/index.tsx | 7 +- ...low_with_prebuilt_rule_customization.cy.ts | 110 ++++++++++++++++++ .../cypress/screens/alerts_detection_rules.ts | 2 + .../cypress/screens/rule_updates.ts | 12 ++ .../cypress/tasks/api_calls/rules.ts | 17 +++ .../cypress/tasks/prebuilt_rules.ts | 10 ++ 18 files changed, 236 insertions(+), 65 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx rename x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/{upgrade_rule_source_filter_popover.tsx => upgrade_rule_customization_filter_popover.tsx} (56%) create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_with_prebuilt_rule_customization.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx index 56a559a91794a..e4b00196f4768 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx @@ -10,7 +10,7 @@ import { EuiBadge } from '@elastic/eui'; import * as i18n from './translations'; import { isCustomizedPrebuiltRule } from '../../../../../common/api/detection_engine'; import type { RuleResponse } from '../../../../../common/api/detection_engine'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../hooks/use_is_prebuilt_rules_customization_enabled'; interface CustomizedPrebuiltRuleBadgeProps { rule: RuleResponse | null; @@ -19,9 +19,7 @@ interface CustomizedPrebuiltRuleBadgeProps { export const CustomizedPrebuiltRuleBadge: React.FC = ({ rule, }) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); if (!isPrebuiltRulesCustomizationEnabled) { return null; @@ -31,5 +29,5 @@ export const CustomizedPrebuiltRuleBadge: React.FC{i18n.CUSTOMIZED_PREBUILT_RULE_LABEL}; + return {i18n.MODIFIED_PREBUILT_RULE_LABEL}; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 89c22a285e327..e7f36e2011f3c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -350,10 +350,10 @@ export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( } ); -export const CUSTOMIZED_PREBUILT_RULE_LABEL = i18n.translate( +export const MODIFIED_PREBUILT_RULE_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.customizedPrebuiltRuleLabel', { - defaultMessage: 'Customized Elastic rule', + defaultMessage: 'Modified Elastic rule', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx index b5ca86c6f1f57..5450cf9950d59 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx @@ -7,7 +7,7 @@ import { useKibana } from '../../../common/lib/kibana/kibana_react'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from './use_is_prebuilt_rules_customization_enabled'; /** * Gets the default index pattern for cases when rule has neither index patterns or data view. @@ -15,9 +15,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_exper */ export function useDefaultIndexPattern(): string[] { const { services } = useKibana(); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); return isPrebuiltRulesCustomizationEnabled ? services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx new file mode 100644 index 0000000000000..d25925860c175 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; + +export const useIsPrebuiltRulesCustomizationEnabled = () => { + return useIsExperimentalFeatureEnabled('prebuiltRulesCustomizationEnabled'); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index 10e094fe84cf4..59ac52d592bcd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -99,7 +99,7 @@ export interface FilterOptions { excludeRuleTypes?: Type[]; enabled?: boolean; // undefined is to display all the rules ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all" - ruleSource?: RuleSourceTypesEnum[]; // undefined is to display all the rules + ruleSource?: RuleCustomizationEnum[]; // undefined is to display all the rules } export interface FetchRulesResponse { @@ -204,7 +204,7 @@ export interface FindRulesReferencedByExceptionsProps { signal?: AbortSignal; } -export enum RuleSourceTypesEnum { - MODIFIED = 'MODIFIED', - UNMODIFIED = 'UNMODIFIED', +export enum RuleCustomizationEnum { + customized = 'CUSTOMIZED', + not_customized = 'NOT_CUSTOMIZED', } diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index bb197a2a02c50..6ec9ffdd02e67 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -8,8 +8,8 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { RuleUpgradeConflictsResolverTab } from '../../../../rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab'; import { PerFieldRuleDiffTab } from '../../../../rule_management/components/rule_details/per_field_rule_diff_tab'; import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages'; @@ -111,9 +111,7 @@ interface UpgradePrebuiltRulesTableContextProviderProps { export const UpgradePrebuiltRulesTableContextProvider = ({ children, }: UpgradePrebuiltRulesTableContextProviderProps) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [loadingRules, setLoadingRules] = useState([]); const [filterOptions, setFilterOptions] = useState({ filter: '', diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx index 16df8799c1f4b..900d81d0b0037 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx @@ -9,13 +9,13 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import type { RuleSourceTypesEnum } from '../../../../rule_management/logic'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; +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 { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; -import { RuleSourceFilterPopover } from './upgrade_rule_source_filter_popover'; +import { RuleCustomizationFilterPopover } from './upgrade_rule_customization_filter_popover'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; @@ -31,9 +31,7 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { actions: { setFilterOptions }, } = useUpgradePrebuiltRulesTableContext(); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const { tags: selectedTags, ruleSource: selectedRuleSource = [] } = filterOptions; @@ -60,7 +58,7 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { ); const handleSelectedRuleSource = useCallback( - (newRuleSource: RuleSourceTypesEnum[]) => { + (newRuleSource: RuleCustomizationEnum[]) => { if (!isEqual(newRuleSource, selectedRuleSource)) { setFilterOptions((filters) => ({ ...filters, @@ -82,10 +80,10 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { {isPrebuiltRulesCustomizationEnabled && ( - )} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx similarity index 56% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx index 5ec22b5341f3a..234943e333272 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_source_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx @@ -8,34 +8,36 @@ import React, { useState, useMemo } from 'react'; import type { EuiSelectableOption } from '@elastic/eui'; import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui'; -import { RuleSourceTypesEnum } from '../../../../rule_management/logic'; +import { RuleCustomizationEnum } from '../../../../rule_management/logic'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; -interface RuleSourceFilterPopoverProps { - selectedRuleSource: RuleSourceTypesEnum[]; - onSelectedRuleSourceChanged: (newRuleSource: RuleSourceTypesEnum[]) => void; +interface RuleCustomizationFilterPopoverProps { + selectedRuleSource: RuleCustomizationEnum[]; + onSelectedRuleSourceChanged: (newRuleSource: RuleCustomizationEnum[]) => void; } -const RULE_SOURCE_POPOVER_WIDTH = 200; +const RULE_CUSTOMIZATION_POPOVER_WIDTH = 200; -const RuleSourceFilterPopoverComponent = ({ +const RuleCustomizationFilterPopoverComponent = ({ selectedRuleSource, onSelectedRuleSourceChanged, -}: RuleSourceFilterPopoverProps) => { - const [isRuleSourcePopoverOpen, setIsRuleSourcePopoverOpen] = useState(false); +}: RuleCustomizationFilterPopoverProps) => { + const [isRuleCustomizationPopoverOpen, setIsRuleCustomizationPopoverOpen] = useState(false); const selectableOptions: EuiSelectableOption[] = useMemo( () => [ { label: i18n.MODIFIED_LABEL, - key: RuleSourceTypesEnum.MODIFIED, - checked: selectedRuleSource.includes(RuleSourceTypesEnum.MODIFIED) ? 'on' : undefined, + key: RuleCustomizationEnum.customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.customized) ? 'on' : undefined, }, { label: i18n.UNMODIFIED_LABEL, - key: RuleSourceTypesEnum.UNMODIFIED, - checked: selectedRuleSource.includes(RuleSourceTypesEnum.UNMODIFIED) ? 'on' : undefined, + key: RuleCustomizationEnum.not_customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.not_customized) + ? 'on' + : undefined, }, ], [selectedRuleSource] @@ -57,12 +59,12 @@ const RuleSourceFilterPopoverComponent = ({ setIsRuleSourcePopoverOpen(!isRuleSourcePopoverOpen)} + onClick={() => setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} numFilters={selectableOptions.length} - isSelected={isRuleSourcePopoverOpen} + isSelected={isRuleCustomizationPopoverOpen} hasActiveFilters={selectedRuleSource.length > 0} numActiveFilters={selectedRuleSource.length} - data-test-subj="rule-source-filter-popover-button" + data-test-subj="rule-customization-filter-popover-button" > {i18n.RULE_SOURCE} @@ -72,19 +74,19 @@ const RuleSourceFilterPopoverComponent = ({ setIsRuleSourcePopoverOpen(!isRuleSourcePopoverOpen)} + isOpen={isRuleCustomizationPopoverOpen} + closePopover={() => setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} panelPaddingSize="none" repositionOnScroll panelProps={{ - 'data-test-subj': 'rule-source-filter-popover', + 'data-test-subj': 'rule-customization-filter-popover', }} > - {(list) =>
{list}
} + {(list) =>
{list}
}
); }; -export const RuleSourceFilterPopover = React.memo(RuleSourceFilterPopoverComponent); +export const RuleCustomizationFilterPopover = React.memo(RuleCustomizationFilterPopoverComponent); diff --git a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts index 0f45ca84ea584..b5a0e123d7510 100644 --- a/x-pack/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/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; import type { RuleUpgradeInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import { RuleSourceTypesEnum, type FilterOptions } from '../../../../rule_management/logic/types'; +import { RuleCustomizationEnum, type FilterOptions } from '../../../../rule_management/logic/types'; export type UpgradePrebuiltRulesTableFilterOptions = Pick< FilterOptions, @@ -34,17 +34,17 @@ export const useFilterPrebuiltRulesToUpgrade = ({ if (ruleSource && ruleSource.length > 0) { if ( - ruleSource.includes(RuleSourceTypesEnum.MODIFIED) && - ruleSource.includes(RuleSourceTypesEnum.UNMODIFIED) + ruleSource.includes(RuleCustomizationEnum.customized) && + ruleSource.includes(RuleCustomizationEnum.not_customized) ) { return true; } else if ( - ruleSource.includes(RuleSourceTypesEnum.MODIFIED) && + ruleSource.includes(RuleCustomizationEnum.customized) && ruleInfo.current_rule.rule_source.type === 'external' ) { return ruleInfo.current_rule.rule_source.is_customized; } else if ( - ruleSource.includes(RuleSourceTypesEnum.UNMODIFIED) && + ruleSource.includes(RuleCustomizationEnum.not_customized) && ruleInfo.current_rule.rule_source.type === 'external' ) { return ruleInfo.current_rule.rule_source.is_customized === false; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 29c5b2b201fe6..8c97a4ef52e2b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -6,7 +6,7 @@ */ import { useCallback, useMemo, useState } from 'react'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState, FieldsUpgradeState, @@ -33,9 +33,7 @@ interface UseRulesUpgradeStateResult { export function usePrebuiltRulesUpgradeState( ruleUpgradeInfos: RuleUpgradeInfoForReview[] ): UseRulesUpgradeStateResult { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [rulesResolvedConflicts, setRulesResolvedConflicts] = useState({}); const setRuleFieldResolvedValue = useCallback( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index a24f109798e9a..579f571f80e79 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -190,6 +190,11 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { } = useUpgradePrebuiltRulesTableContext(); const isDisabled = isRefetching || isUpgradingSecurityPackages; + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ RULE_NAME_COLUMN, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 92ba276ac4f8e..ae24b2bb482d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -10,7 +10,6 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiToolTip } fro import { FormattedMessage } from '@kbn/i18n-react'; import moment from 'moment'; import React, { useMemo } from 'react'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { RulesTableEmptyColumnName } from './rules_table_empty_column_name'; import type { SecurityJob } from '../../../../common/components/ml_popover/types'; import { @@ -47,6 +46,7 @@ import { useRulesTableActions } from './use_rules_table_actions'; import { MlRuleWarningPopover } from '../ml_rule_warning_popover/ml_rule_warning_popover'; import { getMachineLearningJobId } from '../../../../detections/pages/detection_engine/rules/helpers'; import type { TimeRange } from '../../../rule_gaps/types'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -295,9 +295,7 @@ export const useRulesColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -312,6 +310,11 @@ export const useRulesColumns = ({ }); const snoozeColumn = useRuleSnoozeColumn(); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ ruleNameColumn, @@ -415,9 +418,7 @@ export const useMonitoringColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -431,6 +432,11 @@ export const useMonitoringColumns = ({ mlJobs, }); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx index 2d2875d5a8734..49b6c1d1e4e99 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx @@ -16,6 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../../detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RelatedIntegrationArray } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { IntegrationDescription } from '../integrations_description'; import { useRelatedIntegrations } from '../use_related_integrations'; @@ -54,6 +55,7 @@ const IntegrationListItem = styled('li')` const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopoverProps) => { const [isPopoverOpen, setPopoverOpen] = useState(false); const { integrations, isLoaded } = useRelatedIntegrations(relatedIntegrations); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledIntegrations = useMemo(() => { return integrations.filter( @@ -65,10 +67,13 @@ const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopov const numIntegrationsEnabled = enabledIntegrations.length; const badgeTitle = useMemo(() => { + if (isPrebuiltRulesCustomizationEnabled) { + return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations}` : `${numIntegrations}`; + } return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations} ${i18n.INTEGRATIONS_BADGE}` : `${numIntegrations} ${i18n.INTEGRATIONS_BADGE}`; - }, [isLoaded, numIntegrations, numIntegrationsEnabled]); + }, [isLoaded, isPrebuiltRulesCustomizationEnabled, numIntegrations, numIntegrationsEnabled]); return ( { + describe('Upgrade of prebuilt rules with conflicts', () => { + const RULE_1_ID = 'rule_1'; + const RULE_2_ID = 'rule_2'; + const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, + version: 1, + }); + const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, + }); + const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, + }); + const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, + version: 2, + }); + const patchedName = 'A new name that creates a conflict'; + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( + 'updatePrebuiltRules' + ); + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules([OUTDATED_RULE_1, OUTDATED_RULE_2]); + /* Modify one of the rule's name to cause a conflict */ + patchRule(OUTDATED_RULE_1['security-rule'].rule_id, { + name: patchedName, + }); + /* Create a second version of the rule, making it available for update */ + installPrebuiltRuleAssets([UPDATED_RULE_1, UPDATED_RULE_2]); + + visitRulesManagementTable(); + clickRuleUpdatesTab(); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show modified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Modified'); + cy.get(MODIFIED_RULE_BADGE).should('exist'); + + // Verify only rules with customized rule sources are displayed + cy.get(RULES_UPDATES_TABLE).contains(patchedName); + assertRulesNotPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show unmodified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Unmodified'); + cy.get(MODIFIED_RULE_BADGE).should('not.exist'); + + // Verify only rules with non-customized rule sources are displayed + assertRulesPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + cy.get(patchedName).should('not.exist'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts index bbc7a346b252f..e2ce41dee2847 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts @@ -349,3 +349,5 @@ export const ESQL_QUERY_VALUE = '[data-test-subj="esqlQueryPropertyValue"]'; export const PER_FIELD_DIFF_WRAPPER = '[data-test-subj="ruleUpgradePerFieldDiffWrapper"]'; export const PER_FIELD_DIFF_DEFINITION_SECTION = '[data-test-subj="perFieldDiffDefinitionSection"]'; + +export const MODIFIED_RULE_BADGE = '[data-test-subj="upgradeRulesTableModifiedColumnBadge"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts new file mode 100644 index 0000000000000..4b11a4624c3e2 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON = + '[data-test-subj="rule-customization-filter-popover-button"]'; + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL = + '[data-test-subj="rule-customization-filter-popover"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts index 008fc92bd0224..c0ef625f52e35 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts @@ -39,6 +39,23 @@ export const createRule = ( ); }; +export const patchRule = ( + ruleId: string, + updateData: Partial +): Cypress.Chainable> => { + return cy.currentSpace().then((spaceId) => + rootRequest({ + method: 'PATCH', + url: spaceId ? getSpaceUrl(spaceId, DETECTION_ENGINE_RULES_URL) : DETECTION_ENGINE_RULES_URL, + body: { + rule_id: ruleId, + ...updateData, + }, + failOnStatusCode: false, + }) + ); +}; + /** * Snoozes a rule via API * 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 b4f68dba976c3..d4148d5e632a1 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 @@ -17,6 +17,10 @@ import { TOASTER, } from '../screens/alerts_detection_rules'; import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules'; +import { + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON, + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL, +} from '../screens/rule_updates'; export const clickAddElasticRulesButton = () => { cy.get(ADD_ELASTIC_RULES_BTN).click(); @@ -150,3 +154,9 @@ export const assertRulesNotPresentInRuleUpdatesTable = ( cy.get(rule['security-rule'].name).should('not.exist'); } }; + +export const filterPrebuiltRulesUpdateTableByRuleCustomization = (text: string) => { + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL).contains(text).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); +};