[Security Solution] Replace EUI filtering with custom in-memory filtering in Add Rules and Rule Upgrade tables (#159700)

## Summary

**1.** Replaces the EUI out-of-the-box filtering by rule name and tags
[used in the initial
implementation](https://github.com/elastic/kibana/pull/158450) with
custom in-memory filtering.

This aligns the look-and-feel of the Rules Management table with the new
Add Elastic Rules and Rule Upgrades table


![image](e4b01221-74c1-40e5-abf4-87344a080e5d)


![image](9684cee2-a2bf-4850-82e0-1d3679c55c99)


**2.** Adds a CTA in the Add Elastic RUles table when all rules have
been installed, that navigates the user back to the Rules Management
table.


![image](15825af2-005d-47c8-a2a6-97603ea32646)



### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)




### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Juan Pablo Djeredjian 2023-06-19 16:20:20 +02:00 committed by GitHub
parent 6dbf5483cf
commit 2f0c12a00c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 420 additions and 90 deletions

View file

@ -0,0 +1,52 @@
/*
* 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 { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { SecurityPageName } from '../../../../../../common';
import { useGetSecuritySolutionLinkProps } from '../../../../../common/components/links';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
const AddPrebuiltRulesTableNoItemsMessageComponent = () => {
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps();
const { onClick: onClickLink } = getSecuritySolutionLinkProps({
deepLinkId: SecurityPageName.rules,
});
return (
<EuiFlexGroup
alignItems="center"
gutterSize="s"
responsive={false}
direction="column"
wrap={true}
>
<EuiFlexItem grow={false}>
<EuiEmptyPrompt
title={<h3>{i18n.NO_RULES_AVAILABLE_FOR_INSTALL}</h3>}
titleSize="s"
body={i18n.NO_RULES_AVAILABLE_FOR_INSTALL_BODY}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
fill
iconType="arrowLeft"
color={'primary'}
onClick={onClickLink}
data-test-subj="addRulesGoBackToRulesTableBtn"
>
{i18n.GO_BACK_TO_RULES_TABLE_BUTTON}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
};
export const AddPrebuiltRulesTableNoItemsMessage = React.memo(
AddPrebuiltRulesTableNoItemsMessageComponent
);

View file

@ -6,7 +6,6 @@
*/
import {
EuiEmptyPrompt,
EuiInMemoryTable,
EuiSkeletonLoading,
EuiProgress,
@ -15,20 +14,13 @@ import {
} from '@elastic/eui';
import React from 'react';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_items_message';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
import { AddPrebuiltRulesTableFilters } from './add_prebuilt_rules_table_filters';
import { useAddPrebuiltRulesTableColumns } from './use_add_prebuilt_rules_table_columns';
const NO_ITEMS_MESSAGE = (
<EuiEmptyPrompt
title={<h3>{i18n.NO_RULES_AVAILABLE_FOR_INSTALL}</h3>}
titleSize="s"
body={i18n.NO_RULES_AVAILABLE_FOR_INSTALL_BODY}
/>
);
/**
* Table Component for displaying new rules that are available to be installed
*/
@ -38,7 +30,7 @@ export const AddPrebuiltRulesTable = React.memo(() => {
const addRulesTableContext = useAddPrebuiltRulesTableContext();
const {
state: { rules, tags, isFetched, isLoading, isRefetching, selectedRules },
state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
actions: { selectRules },
} = addRulesTableContext;
const rulesColumns = useAddPrebuiltRulesTableColumns();
@ -68,44 +60,28 @@ export const AddPrebuiltRulesTable = React.memo(() => {
}
loadedContent={
isTableEmpty ? (
NO_ITEMS_MESSAGE
<AddPrebuiltRulesTableNoItemsMessage />
) : (
<EuiInMemoryTable
items={rules}
sorting
search={{
box: {
incremental: true,
isClearable: true,
},
filters: [
{
type: 'field_value_selection',
field: 'tags',
name: 'Tags',
multiSelect: true,
options: tags.map((tag) => ({
value: tag,
name: tag,
field: 'tags',
})),
},
],
}}
pagination={{
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
}}
isSelectable
selection={{
selectable: () => true,
onSelectionChange: selectRules,
initialSelected: selectedRules,
}}
itemId="rule_id"
data-test-subj="add-prebuilt-rules-table"
columns={rulesColumns}
/>
<>
<AddPrebuiltRulesTableFilters />
<EuiInMemoryTable
items={filteredRules}
sorting
pagination={{
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
}}
isSelectable
selection={{
selectable: () => true,
onSelectionChange: selectRules,
initialSelected: selectedRules,
}}
itemId="rule_id"
data-test-subj="add-prebuilt-rules-table"
columns={rulesColumns}
/>
</>
)
}
/>

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { Dispatch, SetStateAction } from 'react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import type { RuleInstallationInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_installation/response_schema';
import type { RuleSignatureId } from '../../../../../../common/detection_engine/rule_schema';
@ -14,12 +15,22 @@ import {
usePerformInstallSpecificRules,
} from '../../../../rule_management/logic/prebuilt_rules/use_perform_rule_install';
import { usePrebuiltRulesInstallReview } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_install_review';
import type { AddPrebuiltRulesTableFilterOptions } from './use_filter_prebuilt_rules_to_install';
import { useFilterPrebuiltRulesToInstall } from './use_filter_prebuilt_rules_to_install';
export interface AddPrebuiltRulesTableState {
/**
* Rules available to be installed
*/
rules: RuleInstallationInfoForReview[];
/**
* Rules to display in table after applying filters
*/
filteredRules: RuleInstallationInfoForReview[];
/**
* Currently selected table filter
*/
filterOptions: AddPrebuiltRulesTableFilterOptions;
/**
* All unique tags for all rules
*/
@ -55,6 +66,7 @@ export interface AddPrebuiltRulesTableActions {
installOneRule: (ruleId: RuleSignatureId) => void;
installAllRules: () => void;
installSelectedRules: () => void;
setFilterOptions: Dispatch<SetStateAction<AddPrebuiltRulesTableFilterOptions>>;
selectRules: (rules: RuleInstallationInfoForReview[]) => void;
}
@ -75,6 +87,11 @@ export const AddPrebuiltRulesTableContextProvider = ({
const [loadingRules, setLoadingRules] = useState<RuleSignatureId[]>([]);
const [selectedRules, setSelectedRules] = useState<RuleInstallationInfoForReview[]>([]);
const [filterOptions, setFilterOptions] = useState<AddPrebuiltRulesTableFilterOptions>({
filter: '',
tags: [],
});
const {
data: { rules, stats: { tags } } = {
rules: [],
@ -135,6 +152,7 @@ export const AddPrebuiltRulesTableContextProvider = ({
const actions = useMemo(
() => ({
setFilterOptions,
installAllRules,
installOneRule,
installSelectedRules,
@ -144,10 +162,14 @@ export const AddPrebuiltRulesTableContextProvider = ({
[installAllRules, installOneRule, installSelectedRules, refetch]
);
const filteredRules = useFilterPrebuiltRulesToInstall({ filterOptions, rules });
const providerValue = useMemo<AddPrebuiltRulesContextType>(() => {
return {
state: {
rules,
filteredRules,
filterOptions,
tags,
isFetched,
isLoading,
@ -160,6 +182,8 @@ export const AddPrebuiltRulesTableContextProvider = ({
};
}, [
rules,
filteredRules,
filterOptions,
tags,
isFetched,
isLoading,

View file

@ -0,0 +1,80 @@
/*
* 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 { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isEqual } from 'lodash/fp';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
import { TagsFilterPopover } from '../rules_table_filters/tags_filter_popover';
import { RuleSearchField } from '../rules_table_filters/rule_search_field';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
const FilterWrapper = styled(EuiFlexGroup)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeM};
`;
/**
* Collection of filters for filtering data within the Add Prebuilt Rules table.
* Contains search bar and tag selection
*/
const AddPrebuiltRulesTableFiltersComponent = () => {
const {
state: { filterOptions, tags },
actions: { setFilterOptions },
} = useAddPrebuiltRulesTableContext();
const { tags: selectedTags } = filterOptions;
const handleOnSearch = useCallback(
(filterString) => {
setFilterOptions((filters) => ({
...filters,
filter: filterString.trim(),
}));
},
[setFilterOptions]
);
const handleSelectedTags = useCallback(
(newTags: string[]) => {
if (!isEqual(newTags, selectedTags)) {
setFilterOptions((filters) => ({
...filters,
tags: newTags,
}));
}
},
[selectedTags, setFilterOptions]
);
return (
<FilterWrapper gutterSize="m" justifyContent="flexEnd" wrap>
<RuleSearchField
initialValue={filterOptions.filter}
onSearch={handleOnSearch}
placeholder={i18n.SEARCH_PLACEHOLDER}
/>
<EuiFlexItem grow={false}>
<EuiFilterGroup>
<TagsFilterPopover
onSelectedTagsChanged={handleSelectedTags}
selectedTags={selectedTags}
tags={tags}
data-test-subj="allRulesTagPopover"
/>
</EuiFilterGroup>
</EuiFlexItem>
</FilterWrapper>
);
};
AddPrebuiltRulesTableFiltersComponent.displayName = 'AddPrebuiltRulesTableFiltersComponent';
export const AddPrebuiltRulesTableFilters = React.memo(AddPrebuiltRulesTableFiltersComponent);
AddPrebuiltRulesTableFilters.displayName = 'AddPrebuiltRulesTableFilters';

View file

@ -23,3 +23,10 @@ export const INSTALL_SELECTED_RULES = (numberOfSelectedRules: number) => {
}
);
};
export const SEARCH_PLACEHOLDER = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.addRules.searchBarPlaceholder',
{
defaultMessage: 'Search by rule name',
}
);

View file

@ -0,0 +1,37 @@
/*
* 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 { RuleInstallationInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_installation/response_schema';
import type { FilterOptions } from '../../../../rule_management/logic/types';
export type AddPrebuiltRulesTableFilterOptions = Pick<FilterOptions, 'filter' | 'tags'>;
export const useFilterPrebuiltRulesToInstall = ({
rules,
filterOptions,
}: {
rules: RuleInstallationInfoForReview[];
filterOptions: AddPrebuiltRulesTableFilterOptions;
}) => {
const filteredRules = useMemo(() => {
const { filter, tags } = filterOptions;
return rules.filter((rule) => {
if (filter && !rule.name.toLowerCase().includes(filter.toLowerCase())) {
return false;
}
if (tags && tags.length > 0) {
return tags.every((tag) => rule.tags.includes(tag));
}
return true;
});
}, [filterOptions, rules]);
return filteredRules;
};

View file

@ -25,9 +25,14 @@ const SearchBarWrapper = styled(EuiFlexItem)`
interface RuleSearchFieldProps {
initialValue?: string;
onSearch: (value: string) => void;
placeholder?: string;
}
export function RuleSearchField({ initialValue, onSearch }: RuleSearchFieldProps): JSX.Element {
export function RuleSearchField({
initialValue,
onSearch,
placeholder,
}: RuleSearchFieldProps): JSX.Element {
const [searchText, setSearchText] = useState(initialValue);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value),
@ -45,7 +50,7 @@ export function RuleSearchField({ initialValue, onSearch }: RuleSearchFieldProps
aria-label={i18n.SEARCH_RULES}
fullWidth
incremental={false}
placeholder={i18n.SEARCH_PLACEHOLDER}
placeholder={placeholder ?? i18n.SEARCH_PLACEHOLDER}
value={searchText}
onChange={handleChange}
onSearch={onSearch}

View file

@ -23,3 +23,10 @@ export const UPDATE_SELECTED_RULES = (numberOfSelectedRules: number) => {
}
);
};
export const SEARCH_PLACEHOLDER = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.upgradeRules.searchBarPlaceholder',
{
defaultMessage: 'Search by rule name',
}
);

View file

@ -7,6 +7,8 @@
import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiProgress,
EuiSkeletonLoading,
@ -19,6 +21,7 @@ import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logi
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons';
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';
const NO_ITEMS_MESSAGE = (
@ -38,7 +41,7 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
const upgradeRulesTableContext = useUpgradePrebuiltRulesTableContext();
const {
state: { rules, tags, isFetched, isLoading, isRefetching, selectedRules },
state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
actions: { selectRules },
} = upgradeRulesTableContext;
const rulesColumns = useUpgradePrebuiltRulesTableColumns();
@ -70,43 +73,34 @@ export const UpgradePrebuiltRulesTable = React.memo(() => {
isTableEmpty ? (
NO_ITEMS_MESSAGE
) : (
<EuiInMemoryTable
items={rules}
sorting
search={{
box: {
incremental: true,
isClearable: true,
},
toolsRight: [<UpgradePrebuiltRulesTableButtons />],
filters: [
{
type: 'field_value_selection',
field: 'rule.tags',
name: 'Tags',
multiSelect: true,
options: tags.map((tag) => ({
value: tag,
name: tag,
field: 'rule.tags',
})),
},
],
}}
pagination={{
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
}}
isSelectable
selection={{
selectable: () => true,
onSelectionChange: selectRules,
initialSelected: selectedRules,
}}
itemId="rule_id"
data-test-subj="rules-upgrades-table"
columns={rulesColumns}
/>
<>
<EuiFlexGroup alignItems="flexStart" gutterSize="s" responsive={false} wrap={true}>
<EuiFlexItem grow={true}>
<UpgradePrebuiltRulesTableFilters />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<UpgradePrebuiltRulesTableButtons />
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
items={filteredRules}
sorting
pagination={{
initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE,
pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS,
}}
isSelectable
selection={{
selectable: () => true,
onSelectionChange: selectRules,
initialSelected: selectedRules,
}}
itemId="rule_id"
data-test-subj="rules-upgrades-table"
columns={rulesColumns}
/>
</>
)
}
/>

View file

@ -6,6 +6,7 @@
*/
// import { isEqual } from 'lodash';
import type { Dispatch, SetStateAction } from 'react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import type { RuleUpgradeInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema';
import type { RuleSignatureId } from '../../../../../../common/detection_engine/rule_schema';
@ -15,12 +16,22 @@ import {
usePerformUpgradeSpecificRules,
} from '../../../../rule_management/logic/prebuilt_rules/use_perform_rule_upgrade';
import { usePrebuiltRulesUpgradeReview } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review';
import type { UpgradePrebuiltRulesTableFilterOptions } from './use_filter_prebuilt_rules_to_upgrade';
import { useFilterPrebuiltRulesToUpgrade } from './use_filter_prebuilt_rules_to_upgrade';
export interface UpgradePrebuiltRulesTableState {
/**
* Rules available to be updated
*/
rules: RuleUpgradeInfoForReview[];
/**
* Rules to display in table after applying filters
*/
filteredRules: RuleUpgradeInfoForReview[];
/**
* Currently selected table filter
*/
filterOptions: UpgradePrebuiltRulesTableFilterOptions;
/**
* All unique tags for all rules
*/
@ -57,6 +68,7 @@ export interface UpgradePrebuiltRulesTableActions {
upgradeOneRule: (ruleId: string) => void;
upgradeSelectedRules: () => void;
upgradeAllRules: () => void;
setFilterOptions: Dispatch<SetStateAction<UpgradePrebuiltRulesTableFilterOptions>>;
selectRules: (rules: RuleUpgradeInfoForReview[]) => void;
}
@ -79,6 +91,11 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
const [loadingRules, setLoadingRules] = useState<RuleSignatureId[]>([]);
const [selectedRules, setSelectedRules] = useState<RuleUpgradeInfoForReview[]>([]);
const [filterOptions, setFilterOptions] = useState<UpgradePrebuiltRulesTableFilterOptions>({
filter: '',
tags: [],
});
const {
data: { rules, stats: { tags } } = {
rules: [],
@ -150,15 +167,20 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
upgradeOneRule,
upgradeSelectedRules,
upgradeAllRules,
setFilterOptions,
selectRules: setSelectedRules,
}),
[refetch, upgradeAllRules, upgradeOneRule, upgradeSelectedRules]
);
const filteredRules = useFilterPrebuiltRulesToUpgrade({ filterOptions, rules });
const providerValue = useMemo<UpgradePrebuiltRulesContextType>(() => {
return {
state: {
rules,
filteredRules,
filterOptions,
tags,
isFetched,
isLoading,
@ -171,6 +193,8 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
};
}, [
rules,
filteredRules,
filterOptions,
tags,
isFetched,
isLoading,

View file

@ -0,0 +1,82 @@
/*
* 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 { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isEqual } from 'lodash/fp';
import React, { useCallback } from 'react';
import styled from 'styled-components';
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';
const FilterWrapper = styled(EuiFlexGroup)`
margin-bottom: ${({ theme }) => theme.eui.euiSizeM};
`;
/**
* Collection of filters for filtering data within the Upgrade Prebuilt Rules table.
* Contains search bar and tag selection
*/
const UpgradePrebuiltRulesTableFiltersComponent = () => {
const {
state: { filterOptions, tags },
actions: { setFilterOptions },
} = useUpgradePrebuiltRulesTableContext();
const { tags: selectedTags } = filterOptions;
const handleOnSearch = useCallback(
(filterString) => {
setFilterOptions((filters) => ({
...filters,
filter: filterString.trim(),
}));
},
[setFilterOptions]
);
const handleSelectedTags = useCallback(
(newTags: string[]) => {
if (!isEqual(newTags, selectedTags)) {
setFilterOptions((filters) => ({
...filters,
tags: newTags,
}));
}
},
[selectedTags, setFilterOptions]
);
return (
<FilterWrapper gutterSize="m" justifyContent="flexEnd" wrap>
<RuleSearchField
initialValue={filterOptions.filter}
onSearch={handleOnSearch}
placeholder={i18n.SEARCH_PLACEHOLDER}
/>
<EuiFlexItem grow={false}>
<EuiFilterGroup>
<TagsFilterPopover
onSelectedTagsChanged={handleSelectedTags}
selectedTags={selectedTags}
tags={tags}
data-test-subj="upgradeRulesTagPopover"
/>
</EuiFilterGroup>
</EuiFlexItem>
</FilterWrapper>
);
};
UpgradePrebuiltRulesTableFiltersComponent.displayName = 'UpgradePrebuiltRulesTableFiltersComponent';
export const UpgradePrebuiltRulesTableFilters = React.memo(
UpgradePrebuiltRulesTableFiltersComponent
);
UpgradePrebuiltRulesTableFilters.displayName = 'UpgradePrebuiltRulesTableFilters';

View file

@ -0,0 +1,37 @@
/*
* 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 { RuleUpgradeInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema';
import type { FilterOptions } from '../../../../rule_management/logic/types';
export type UpgradePrebuiltRulesTableFilterOptions = Pick<FilterOptions, 'filter' | 'tags'>;
export const useFilterPrebuiltRulesToUpgrade = ({
rules,
filterOptions,
}: {
rules: RuleUpgradeInfoForReview[];
filterOptions: UpgradePrebuiltRulesTableFilterOptions;
}) => {
const filteredRules = useMemo(() => {
const { filter, tags } = filterOptions;
return rules.filter(({ rule }) => {
if (filter && !rule.name.toLowerCase().includes(filter.toLowerCase())) {
return false;
}
if (tags && tags.length > 0) {
return tags.every((tag) => rule.tags.includes(tag));
}
return true;
});
}, [filterOptions, rules]);
return filteredRules;
};

View file

@ -14,8 +14,6 @@ import { useGetSecuritySolutionLinkProps } from '../../../../common/components/l
import { SecurityPageName } from '../../../../../common';
import { usePrebuiltRulesStatus } from '../../../../detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_status';
// TODO: Still need to load timeline templates
interface LoadPrePackagedRulesButtonProps {
'data-test-subj'?: string;
fill?: boolean;

View file

@ -1211,3 +1211,10 @@ export const UPDATE_RULE_BUTTON = i18n.translate(
defaultMessage: 'Update rule',
}
);
export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate(
'xpack.securitySolution.addRules.goBackToRulesTableButton',
{
defaultMessage: 'Go back to installed Elastic rules',
}
);