mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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   **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.  ### 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:
parent
6dbf5483cf
commit
2f0c12a00c
14 changed files with 420 additions and 90 deletions
|
@ -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
|
||||
);
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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}
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue