diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 591c7d68e17cb..f7bdc889f9c33 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -443,7 +443,7 @@ export const RULES_TABLE_PAGE_SIZE_OPTIONS = [5, 10, 20, 50, RULES_TABLE_MAX_PAG * we will need to update this constant with the corresponding version. */ export const RULES_MANAGEMENT_FEATURE_TOUR_STORAGE_KEY = - 'securitySolution.rulesManagementPage.newFeaturesTour.v8.1'; + 'securitySolution.rulesManagementPage.newFeaturesTour.v8.2'; export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY = 'securitySolution.ruleDetails.ruleExecutionLog.showMetrics.v8.2'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts index c8d8b5bb6ffd0..3c534ca7294a5 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts @@ -143,7 +143,7 @@ describe('Detections Rules API', () => { method: 'GET', query: { filter: - '(alert.attributes.name: "hello world" OR alert.attributes.params.index: "hello world" OR alert.attributes.params.threat.tactic.id: "hello world" OR alert.attributes.params.threat.tactic.name: "hello world" OR alert.attributes.params.threat.technique.id: "hello world" OR alert.attributes.params.threat.technique.name: "hello world")', + '(alert.attributes.name: "hello world" OR alert.attributes.params.index: "hello world" OR alert.attributes.params.threat.tactic.id: "hello world" OR alert.attributes.params.threat.tactic.name: "hello world" OR alert.attributes.params.threat.technique.id: "hello world" OR alert.attributes.params.threat.technique.name: "hello world" OR alert.attributes.params.threat.technique.subtechnique.id: "hello world" OR alert.attributes.params.threat.technique.subtechnique.name: "hello world")', page: 1, per_page: 20, sort_field: 'enabled', @@ -172,7 +172,7 @@ describe('Detections Rules API', () => { method: 'GET', query: { filter: - '(alert.attributes.name: "\\" OR (foo:bar)" OR alert.attributes.params.index: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo:bar)")', + '(alert.attributes.name: "\\" OR (foo:bar)" OR alert.attributes.params.index: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" OR (foo:bar)")', page: 1, per_page: 20, sort_field: 'enabled', @@ -383,7 +383,7 @@ describe('Detections Rules API', () => { method: 'GET', query: { filter: - 'alert.attributes.tags: "__internal_immutable:false" AND alert.attributes.tags: "__internal_immutable:true" AND alert.attributes.tags:("hello" AND "world") AND (alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName")', + 'alert.attributes.tags: "__internal_immutable:false" AND alert.attributes.tags: "__internal_immutable:true" AND alert.attributes.tags:("hello" AND "world") AND (alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.id: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.name: "ruleName")', page: 1, per_page: 20, sort_field: 'enabled', diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts index e3d2300972a51..a26a4aec3ec02 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts @@ -27,7 +27,7 @@ describe('convertRulesFilterToKQL', () => { const kql = convertRulesFilterToKQL({ ...filterOptions, filter: 'foo' }); expect(kql).toBe( - '(alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo")' + '(alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo")' ); }); @@ -35,7 +35,7 @@ describe('convertRulesFilterToKQL', () => { const kql = convertRulesFilterToKQL({ ...filterOptions, filter: '" OR (foo: bar)' }); expect(kql).toBe( - '(alert.attributes.name: "\\" OR (foo: bar)" OR alert.attributes.params.index: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo: bar)")' + '(alert.attributes.name: "\\" OR (foo: bar)" OR alert.attributes.params.index: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" OR (foo: bar)")' ); }); @@ -66,7 +66,7 @@ describe('convertRulesFilterToKQL', () => { }); expect(kql).toBe( - `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true" AND alert.attributes.tags:(\"tag1\" AND \"tag2\") AND (alert.attributes.name: \"foo\" OR alert.attributes.params.index: \"foo\" OR alert.attributes.params.threat.tactic.id: \"foo\" OR alert.attributes.params.threat.tactic.name: \"foo\" OR alert.attributes.params.threat.technique.id: \"foo\" OR alert.attributes.params.threat.technique.name: \"foo\")` + `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true" AND alert.attributes.tags:("tag1" AND "tag2") AND (alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo")` ); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts index f5e52fd6362c1..069746223731c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts @@ -16,6 +16,8 @@ const SEARCHABLE_RULE_PARAMS = [ 'alert.attributes.params.threat.tactic.name', 'alert.attributes.params.threat.technique.id', 'alert.attributes.params.threat.technique.name', + 'alert.attributes.params.threat.technique.subtechnique.id', + 'alert.attributes.params.threat.technique.subtechnique.name', ]; /** diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour_context.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour_context.tsx index aaa483e49fca7..b0f0b0f17923c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour_context.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour_context.tsx @@ -43,23 +43,13 @@ const tourConfig: EuiTourState = { const stepsConfig: EuiStatelessTourStep[] = [ { step: 1, - title: 'A new feature', - content:

{'This feature allows for...'}

, - stepsTotal: 2, + title: i18n.SEARCH_CAPABILITIES_TITLE, + content:

{i18n.SEARCH_CAPABILITIES_DESCRIPTION}

, + stepsTotal: 1, children: <>, onFinish: noop, maxWidth: TOUR_POPOVER_WIDTH, }, - { - step: 2, - title: 'Another feature', - content:

{'This another feature allows for...'}

, - stepsTotal: 2, - children: <>, - onFinish: noop, - anchorPosition: 'rightUp', - maxWidth: TOUR_POPOVER_WIDTH, - }, ]; const RulesFeatureTourContext = createContext(null); @@ -82,39 +72,43 @@ export const RulesFeatureTourContextProvider: FC = ({ children }) => { const [tourSteps, tourActions, tourState] = useEuiTour(stepsConfig, restoredState); - const enhancedSteps = useMemo(() => { - return tourSteps.map((item, index, array) => { - return { + const enhancedSteps = useMemo( + () => + tourSteps.map((item, index) => ({ ...item, content: ( <> {item.content} - - - - - - - - - + {tourSteps.length > 1 && ( + <> + + + + + + + + + + + )} ), - }; - }); - }, [tourSteps, tourActions]); + })), + [tourSteps, tourActions] + ); const providerValue = useMemo( () => ({ steps: enhancedSteps, actions: tourActions }), diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts index bfcda64bb13dd..88b5489b01eaa 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts @@ -13,3 +13,18 @@ export const TOUR_TITLE = i18n.translate( defaultMessage: "What's new", } ); + +export const SEARCH_CAPABILITIES_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.featureTour.searchCapabilitiesTitle', + { + defaultMessage: 'Enhanced search capabilities', + } +); + +export const SEARCH_CAPABILITIES_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.featureTour.searchCapabilitiesDescription', + { + defaultMessage: + 'It is now possible to search rules by index patterns, like "filebeat-*", or by MITRE ATT&CK™ tactics or techniques, like "Defense Evasion" or "TA0005".', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index 3b24dda539174..8e44475a7992e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -12,6 +12,7 @@ import { useKibana } from '../../../../../common/lib/kibana'; import { TestProviders } from '../../../../../common/mock'; import '../../../../../common/mock/formatted_relative'; import '../../../../../common/mock/match_media'; +import { RulesFeatureTourContextProvider } from './feature_tour/rules_feature_tour_context'; import { AllRules } from './index'; jest.mock('../../../../../common/components/link_to'); @@ -67,7 +68,8 @@ describe('AllRules', () => { rulesNotInstalled={0} rulesNotUpdated={0} /> - + , + { wrappingComponent: RulesFeatureTourContextProvider } ); await waitFor(() => { @@ -90,7 +92,8 @@ describe('AllRules', () => { rulesNotInstalled={0} rulesNotUpdated={0} /> - + , + { wrappingComponent: RulesFeatureTourContextProvider } ); await waitFor(() => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx index 816ffdfa9dad6..88b8e952d215a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -10,13 +10,16 @@ import { mount } from 'enzyme'; import { RulesTableFilters } from './rules_table_filters'; import { TestProviders } from '../../../../../../common/mock'; +import { RulesFeatureTourContextProvider } from '../feature_tour/rules_feature_tour_context'; jest.mock('../rules_table/rules_table_context'); describe('RulesTableFilters', () => { it('renders no numbers next to rule type button filter if none exist', async () => { const wrapper = mount( - , + + + , { wrappingComponent: TestProviders } ); @@ -30,7 +33,9 @@ describe('RulesTableFilters', () => { it('renders number of custom and prepackaged rules', async () => { const wrapper = mount( - , + + + , { wrappingComponent: TestProviders } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index b4c81ae5a177d..7e3263f6bb26a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -11,11 +11,13 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem, + EuiTourStep, } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import * as i18n from '../../translations'; +import { useRulesFeatureTourContext } from '../feature_tour/rules_feature_tour_context'; import { useRulesTableContext } from '../rules_table/rules_table_context'; import { TagsFilterPopover } from './tags_filter_popover'; @@ -23,6 +25,14 @@ const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeXS}; `; +const SearchBarWrapper = styled(EuiFlexItem)` + & .euiPopover__anchor { + // This is needed to "cancel" styles passed down from EuiTourStep that + // interfere with EuiFieldSearch and don't allow it to take the full width + display: block; + } +`; + interface RulesTableFiltersProps { rulesCustomInstalled: number | null; rulesInstalled: number | null; @@ -45,6 +55,8 @@ const RulesTableFiltersComponent = ({ const { showCustomRules, showElasticRules, tags: selectedTags } = filterOptions; + const { steps } = useRulesFeatureTourContext(); + const handleOnSearch = useCallback( (filterString) => setFilterOptions({ filter: filterString.trim() }), [setFilterOptions] @@ -69,15 +81,17 @@ const RulesTableFiltersComponent = ({ return ( - - - + + + + + { const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); @@ -177,77 +178,79 @@ const RulesPageComponent: React.FC = () => { showCheckBox /> - - - - - {loadPrebuiltRulesAndTemplatesButton && ( - {loadPrebuiltRulesAndTemplatesButton} - )} - {reloadPrebuiltRulesAndTemplatesButton && ( - {reloadPrebuiltRulesAndTemplatesButton} - )} - - + + + + + + {loadPrebuiltRulesAndTemplatesButton && ( + {loadPrebuiltRulesAndTemplatesButton} + )} + {reloadPrebuiltRulesAndTemplatesButton && ( + {reloadPrebuiltRulesAndTemplatesButton} + )} + + + + {i18n.UPLOAD_VALUE_LISTS} + + + + - {i18n.UPLOAD_VALUE_LISTS} + {i18n.IMPORT_RULE} - - - - - {i18n.IMPORT_RULE} - - - - - {i18n.ADD_NEW_RULE} - - - - - {(prePackagedRuleStatus === 'ruleNeedUpdate' || - prePackagedTimelineStatus === 'timelineNeedUpdate') && ( - + + + {i18n.ADD_NEW_RULE} + + + + + {(prePackagedRuleStatus === 'ruleNeedUpdate' || + prePackagedTimelineStatus === 'timelineNeedUpdate') && ( + + )} + - )} - - - + + + 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 f99ebc2c72c26..09949cc5c1a09 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 @@ -408,7 +408,8 @@ export const SEARCH_RULES = i18n.translate( export const SEARCH_PLACEHOLDER = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.searchPlaceholder', { - defaultMessage: 'Search by rule name, index pattern, or MITRE ATT&CK tactic or technique', + defaultMessage: + 'Rule name, index pattern (e.g., "filebeat-*"), or MITRE ATT&CK™ tactic or technique (e.g., "Defense Evasion" or "TA0005")', } );