[SIEM] [Detection Engine] All Rules fixes (#55641)

## Summary

This PR addresses bugs outlined in https://github.com/elastic/kibana/issues/54935

Including:
* Add Risk Score column
* Remove Method column
* Fixes `Showing Events` on Alerts table
* Fixes Tag overflow
* Shows Tags/Filters

### Checklist

Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR.

- [ ] ~This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~
- [x] 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/master/packages/kbn-i18n/README.md)
- [ ] ~[Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~
- [ ] ~[Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~
- [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~

### For maintainers

- [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~
- [ ] ~This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~
This commit is contained in:
Garrett Spong 2020-01-22 18:33:34 -07:00 committed by GitHub
parent da5710dac6
commit 90e3bb0a62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 425 additions and 91 deletions

View file

@ -64,6 +64,7 @@ const AlertsTableComponent: React.FC<Props> = ({ endDate, startDate, pageFilters
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT,
}),
[]
);

View file

@ -154,20 +154,18 @@ const EventsViewerComponent: React.FC<Props> = ({
const totalCountMinusDeleted =
totalCount > 0 ? totalCount - deletedEventIds.length : 0;
const subtitle = `${
i18n.SHOWING
}: ${totalCountMinusDeleted.toLocaleString()} ${timelineTypeContext.unit?.(
totalCountMinusDeleted
) ?? i18n.UNIT(totalCountMinusDeleted)}`;
// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
return (
<>
<HeaderSection
id={id}
subtitle={
utilityBar
? undefined
: `${
i18n.SHOWING
}: ${totalCountMinusDeleted.toLocaleString()} ${i18n.UNIT(
totalCountMinusDeleted
)}`
}
subtitle={utilityBar ? undefined : subtitle}
title={timelineTypeContext?.title ?? i18n.EVENTS}
>
{headerFilterGroup}

View file

@ -23,6 +23,7 @@ export interface TimelineTypeContextProps {
selectAll?: boolean;
timelineActions?: TimelineAction[];
title?: string;
unit?: (totalCount: number) => string;
}
const initTimelineType: TimelineTypeContextProps = {
documentType: undefined,
@ -32,6 +33,7 @@ const initTimelineType: TimelineTypeContextProps = {
selectAll: false,
timelineActions: [],
title: undefined,
unit: undefined,
};
export const TimelineTypeContext = createContext<TimelineTypeContextProps>(initTimelineType);
export const useTimelineTypeContext = () => useContext(TimelineTypeContext);

View file

@ -28,6 +28,7 @@ import {
DETECTION_ENGINE_PREPACKAGED_URL,
DETECTION_ENGINE_RULES_STATUS_URL,
DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL,
DETECTION_ENGINE_TAGS_URL,
} from '../../../../common/constants';
import * as i18n from '../../../pages/detection_engine/rules/translations';
@ -54,61 +55,72 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise<NewRule>
};
/**
* Fetches all rules or single specified rule from the Detection Engine API
* Fetches all rules from the Detection Engine API
*
* @param filterOptions desired filters (e.g. filter/sortField/sortOrder)
* @param pagination desired pagination options (e.g. page/perPage)
* @param id if specified, will return specific rule if exists
* @param signal to cancel request
*
*/
export const fetchRules = async ({
filterOptions = {
filter: '',
sortField: 'name',
sortField: 'enabled',
sortOrder: 'desc',
showCustomRules: false,
showElasticRules: false,
tags: [],
},
pagination = {
page: 1,
perPage: 20,
total: 0,
},
id,
signal,
}: FetchRulesProps): Promise<FetchRulesResponse> => {
const filters = [
...(filterOptions.filter.length !== 0
? [`alert.attributes.name:%20${encodeURIComponent(filterOptions.filter)}`]
: []),
...(filterOptions.showCustomRules
? ['alert.attributes.tags:%20%22__internal_immutable:false%22']
: []),
...(filterOptions.showElasticRules
? ['alert.attributes.tags:%20%22__internal_immutable:true%22']
: []),
...(filterOptions.tags?.map(t => `alert.attributes.tags:${encodeURIComponent(t)}`) ?? []),
];
const queryParams = [
`page=${pagination.page}`,
`per_page=${pagination.perPage}`,
`sort_field=${filterOptions.sortField}`,
`sort_order=${filterOptions.sortOrder}`,
...(filterOptions.filter.length !== 0
? [`filter=alert.attributes.name:%20${encodeURIComponent(filterOptions.filter)}`]
: []),
...(filters.length > 0 ? [`filter=${filters.join('%20AND%20')}`] : []),
];
const endpoint =
id != null
? `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id="${id}"`
: `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_find?${queryParams.join('&')}`;
const response = await fetch(endpoint, {
method: 'GET',
signal,
});
const response = await fetch(
`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_find?${queryParams.join('&')}`,
{
method: 'GET',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
signal,
}
);
await throwIfNotOk(response);
return id != null
? {
page: 0,
perPage: 1,
total: 1,
data: response.json(),
}
: response.json();
return response.json();
};
/**
* Fetch a Rule by providing a Rule ID
*
* @param id Rule ID's (not rule_id)
* @param signal to cancel request
*
*/
export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise<Rule> => {
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, {
@ -344,6 +356,27 @@ export const getRuleStatusById = async ({
return response.json();
};
/**
* Fetch all unique Tags used by Rules
*
* @param signal to cancel request
*
*/
export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise<string[]> => {
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_TAGS_URL}`, {
method: 'GET',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
signal,
});
await throwIfNotOk(response);
return response.json();
};
/**
* Get pre packaged rules Status
*

View file

@ -30,3 +30,10 @@ export const RULE_PREPACKAGED_SUCCESS = i18n.translate(
defaultMessage: 'Installed pre-packaged rules from elastic',
}
);
export const TAG_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.detectionEngine.tagFetchFailDescription',
{
defaultMessage: 'Failed to fetch Tags',
}
);

View file

@ -114,7 +114,6 @@ export interface PaginationOptions {
export interface FetchRulesProps {
pagination?: PaginationOptions;
filterOptions?: FilterOptions;
id?: string;
signal: AbortSignal;
}
@ -122,6 +121,9 @@ export interface FilterOptions {
filter: string;
sortField: string;
sortOrder: 'asc' | 'desc';
showCustomRules?: boolean;
showElasticRules?: boolean;
tags?: string[];
}
export interface FetchRulesResponse {

View file

@ -70,6 +70,9 @@ export const useRules = (pagination: PaginationOptions, filterOptions: FilterOpt
filterOptions.filter,
filterOptions.sortField,
filterOptions.sortOrder,
filterOptions.tags?.sort().join(),
filterOptions.showCustomRules,
filterOptions.showElasticRules,
]);
return [loading, rules, reFetchRules.current];

View file

@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useEffect, useState } from 'react';
import { useStateToaster } from '../../../components/toasters';
import { fetchTags } from './api';
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
import * as i18n from './translations';
type Return = [boolean, string[]];
/**
* Hook for using the list of Tags from the Detection Engine API
*
*/
export const useTags = (): Return => {
const [tags, setTags] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [, dispatchToaster] = useStateToaster();
useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
async function fetchData() {
setLoading(true);
try {
const fetchTagsResult = await fetchTags({
signal: abortCtrl.signal,
});
if (isSubscribed) {
setTags(fetchTagsResult);
}
} catch (error) {
if (isSubscribed) {
errorToToaster({ title: i18n.TAG_FETCH_FAILURE, error, dispatchToaster });
}
}
if (isSubscribed) {
setLoading(false);
}
}
fetchData();
return () => {
isSubscribed = false;
abortCtrl.abort();
};
}, []);
return [loading, tags];
};

View file

@ -56,13 +56,10 @@ export const mockTableData: TableData[] = [
id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61',
immutable: false,
isLoading: false,
lastCompletedRun: undefined,
lastResponse: { type: '—' },
method: 'saved_query',
risk_score: 21,
rule: {
href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61',
name: 'Home Grown!',
status: 'Status Placeholder',
},
rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea',
severity: 'low',
@ -108,13 +105,10 @@ export const mockTableData: TableData[] = [
id: '63f06f34-c181-4b2d-af35-f2ace572a1ee',
immutable: false,
isLoading: false,
lastCompletedRun: undefined,
lastResponse: { type: '—' },
method: 'saved_query',
risk_score: 21,
rule: {
href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee',
name: 'Home Grown!',
status: 'Status Placeholder',
},
rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea',
severity: 'low',

View file

@ -31,6 +31,7 @@ import { RuleSwitch } from '../components/rule_switch';
import { SeverityBadge } from '../components/severity_badge';
import { ActionToaster } from '../../../../components/toasters';
import { getStatusColor } from '../components/rule_status/helpers';
import { TruncatableText } from '../../../../components/truncatable_text';
const getActions = (
dispatch: React.Dispatch<Action>,
@ -84,8 +85,8 @@ export const getColumns = (
width: '24%',
},
{
field: 'method',
name: i18n.COLUMN_METHOD,
field: 'risk_score',
name: i18n.COLUMN_RISK_SCORE,
truncateText: true,
width: '14%',
},
@ -129,13 +130,13 @@ export const getColumns = (
field: 'tags',
name: i18n.COLUMN_TAGS,
render: (value: TableData['tags']) => (
<>
<TruncatableText>
{value.map((tag, i) => (
<EuiBadge color="hollow" key={`${tag}-${i}`}>
{tag}
</EuiBadge>
))}
</>
</TruncatableText>
),
truncateText: true,
width: '20%',

View file

@ -10,7 +10,6 @@ import {
RuleResponseBuckets,
} from '../../../../containers/detection_engine/rules';
import { TableData } from '../types';
import { getEmptyValue } from '../../../../components/empty_value';
/**
* Formats rules into the correct format for the AllRulesTable
@ -26,14 +25,9 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[]
rule: {
href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`,
name: rule.name,
status: 'Status Placeholder',
},
method: rule.type, // TODO: Map to i18n?
risk_score: rule.risk_score,
severity: rule.severity,
lastCompletedRun: undefined, // TODO: Not available yet
lastResponse: {
type: getEmptyValue(), // TODO: Not available yet
},
tags: rule.tags ?? [],
activate: rule.enabled,
status: rule.status ?? null,

View file

@ -6,8 +6,9 @@
import {
EuiBasicTable,
EuiButton,
EuiContextMenuPanel,
EuiFieldSearch,
EuiEmptyPrompt,
EuiLoadingContent,
EuiSpacer,
} from '@elastic/eui';
@ -16,7 +17,11 @@ import React, { useCallback, useEffect, useMemo, useReducer, useState } from 're
import { useHistory } from 'react-router-dom';
import uuid from 'uuid';
import { useRules, CreatePreBuiltRules } from '../../../../containers/detection_engine/rules';
import {
useRules,
CreatePreBuiltRules,
FilterOptions,
} from '../../../../containers/detection_engine/rules';
import { HeaderSection } from '../../../../components/header_section';
import {
UtilityBar,
@ -36,6 +41,8 @@ import { EuiBasicTableOnChange, TableData } from '../types';
import { getBatchItems } from './batch_actions';
import { getColumns } from './columns';
import { allRulesReducer, State } from './reducer';
import { RulesTableFilters } from './rules_table_filters/rules_table_filters';
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
const initialState: State = {
isLoading: true,
@ -209,6 +216,20 @@ export const AllRules = React.memo<AllRulesProps>(
[]
);
const onFilterChangedCallback = useCallback((newFilterOptions: Partial<FilterOptions>) => {
dispatch({
type: 'updateFilterOptions',
filterOptions: {
...filterOptions,
...newFilterOptions,
},
});
dispatch({
type: 'updatePagination',
pagination: { ...pagination, page: 1 },
});
}, []);
return (
<>
<RuleDownloader
@ -231,26 +252,8 @@ export const AllRules = React.memo<AllRulesProps>(
<Panel loading={isGlobalLoading}>
<>
{rulesInstalled != null && rulesInstalled > 0 && (
<HeaderSection split title={i18n.ALL_RULES} border={true}>
<EuiFieldSearch
aria-label={i18n.SEARCH_RULES}
fullWidth
incremental={false}
placeholder={i18n.SEARCH_PLACEHOLDER}
onSearch={filterString => {
dispatch({
type: 'updateFilterOptions',
filterOptions: {
...filterOptions,
filter: filterString,
},
});
dispatch({
type: 'updatePagination',
pagination: { ...pagination, page: 1 },
});
}}
/>
<HeaderSection split title={i18n.ALL_RULES}>
<RulesTableFilters onFilterChanged={onFilterChangedCallback} />
</HeaderSection>
)}
{isInitialLoad && isEmpty(tableData) && (
@ -301,6 +304,24 @@ export const AllRules = React.memo<AllRulesProps>(
isSelectable={!hasNoPermissions ?? false}
itemId="id"
items={tableData}
noItemsMessage={
<EuiEmptyPrompt
title={<h3>{i18n.NO_RULES}</h3>}
titleSize="xs"
body={i18n.NO_RULES_BODY}
actions={
<EuiButton
fill
size="s"
href={`#${DETECTION_ENGINE_PAGE_NAME}/rules/create`}
iconType="plusInCircle"
isDisabled={hasNoPermissions}
>
{i18n.ADD_NEW_RULE}
</EuiButton>
}
/>
}
onChange={tableOnChangeCallback}
pagination={{
pageIndex: pagination.page - 1,

View file

@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback, useEffect, useState } from 'react';
import {
EuiFieldSearch,
EuiFilterButton,
EuiFilterGroup,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import * as i18n from '../../translations';
import { FilterOptions } from '../../../../../containers/detection_engine/rules';
import { useTags } from '../../../../../containers/detection_engine/rules/use_tags';
import { TagsFilterPopover } from './tags_filter_popover';
interface RulesTableFiltersProps {
onFilterChanged: (filterOptions: Partial<FilterOptions>) => void;
}
/**
* Collection of filters for filtering data within the RulesTable. Contains search bar, Elastic/Custom
* Rules filter button toggle, and tag selection
*
* @param onFilterChanged change listener to be notified on filter changes
*/
const RulesTableFiltersComponent = ({ onFilterChanged }: RulesTableFiltersProps) => {
const [filter, setFilter] = useState<string>('');
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [showCustomRules, setShowCustomRules] = useState<boolean>(false);
const [showElasticRules, setShowElasticRules] = useState<boolean>(false);
const [isLoadingTags, tags] = useTags();
// Propagate filter changes to parent
useEffect(() => {
onFilterChanged({ filter, showCustomRules, showElasticRules, tags: selectedTags });
}, [filter, selectedTags, showCustomRules, showElasticRules, onFilterChanged]);
const handleOnSearch = useCallback(filterString => setFilter(filterString.trim()), [setFilter]);
const handleElasticRulesClick = useCallback(() => {
setShowElasticRules(!showElasticRules);
setShowCustomRules(false);
}, [setShowElasticRules, showElasticRules, setShowCustomRules]);
const handleCustomRulesClick = useCallback(() => {
setShowCustomRules(!showCustomRules);
setShowElasticRules(false);
}, [setShowElasticRules, showCustomRules, setShowCustomRules]);
return (
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd">
<EuiFlexItem grow={true}>
<EuiFieldSearch
aria-label={i18n.SEARCH_RULES}
fullWidth
incremental={false}
placeholder={i18n.SEARCH_PLACEHOLDER}
onSearch={handleOnSearch}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFilterGroup>
<TagsFilterPopover
tags={tags}
onSelectedTagsChanged={setSelectedTags}
isLoading={isLoadingTags}
/>
</EuiFilterGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFilterGroup>
<EuiFilterButton
hasActiveFilters={showElasticRules}
onClick={handleElasticRulesClick}
data-test-subj="show-elastic-rules-filter-button"
withNext
>
{i18n.ELASTIC_RULES}
</EuiFilterButton>
<EuiFilterButton
hasActiveFilters={showCustomRules}
onClick={handleCustomRulesClick}
data-test-subj="show-custom-rules-filter-button"
>
{i18n.CUSTOM_RULES}
</EuiFilterButton>
</EuiFilterGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
};
RulesTableFiltersComponent.displayName = 'RulesTableFiltersComponent';
export const RulesTableFilters = React.memo(RulesTableFiltersComponent);
RulesTableFilters.displayName = 'RulesTableFilters';

View file

@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
import {
EuiFilterButton,
EuiFilterSelectItem,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPopover,
EuiText,
} from '@elastic/eui';
import styled from 'styled-components';
import * as i18n from '../../translations';
import { toggleSelectedGroup } from '../../../../../components/ml_popover/jobs_table/filters/toggle_selected_group';
interface TagsFilterPopoverProps {
tags: string[];
onSelectedTagsChanged: Dispatch<SetStateAction<string[]>>;
isLoading: boolean;
}
const ScrollableDiv = styled.div`
max-height: 250px;
overflow: auto;
`;
/**
* Popover for selecting tags to filter on
*
* @param tags to display for filtering
* @param onSelectedTagsChanged change listener to be notified when tag selection changes
*/
export const TagsFilterPopoverComponent = ({
tags,
onSelectedTagsChanged,
}: TagsFilterPopoverProps) => {
const [isTagPopoverOpen, setIsTagPopoverOpen] = useState(false);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
useEffect(() => {
onSelectedTagsChanged(selectedTags);
}, [selectedTags.sort().join()]);
return (
<EuiPopover
ownFocus
button={
<EuiFilterButton
data-test-subj={'tags-filter-popover-button'}
iconType="arrowDown"
onClick={() => setIsTagPopoverOpen(!isTagPopoverOpen)}
isSelected={isTagPopoverOpen}
hasActiveFilters={selectedTags.length > 0}
numActiveFilters={selectedTags.length}
>
{i18n.TAGS}
</EuiFilterButton>
}
isOpen={isTagPopoverOpen}
closePopover={() => setIsTagPopoverOpen(!isTagPopoverOpen)}
panelPaddingSize="none"
>
<ScrollableDiv>
{tags.map((tag, index) => (
<EuiFilterSelectItem
checked={selectedTags.includes(tag) ? 'on' : undefined}
key={`${index}-${tag}`}
onClick={() => toggleSelectedGroup(tag, selectedTags, setSelectedTags)}
>
{`${tag}`}
</EuiFilterSelectItem>
))}
</ScrollableDiv>
{tags.length === 0 && (
<EuiFlexGroup gutterSize="m" justifyContent="spaceAround">
<EuiFlexItem grow={true}>
<EuiPanel>
<EuiText>{i18n.NO_TAGS_AVAILABLE}</EuiText>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiPopover>
);
};
TagsFilterPopoverComponent.displayName = 'TagsFilterPopoverComponent';
export const TagsFilterPopover = React.memo(TagsFilterPopoverComponent);
TagsFilterPopover.displayName = 'TagsFilterPopover';

View file

@ -213,10 +213,10 @@ export const COLUMN_RULE = i18n.translate(
}
);
export const COLUMN_METHOD = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.columns.methodTitle',
export const COLUMN_RISK_SCORE = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.columns.riskScoreTitle',
{
defaultMessage: 'Method',
defaultMessage: 'Risk score',
}
);
@ -255,16 +255,42 @@ export const COLUMN_ACTIVATE = i18n.translate(
}
);
export const COLUMN_STATUS = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.columns.currentStatusTitle',
export const CUSTOM_RULES = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.filters.customRulesTitle',
{
defaultMessage: 'Current status',
defaultMessage: 'Custom rules',
}
);
export const NO_STATUS = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.columns.unknownStatusDescription',
export const ELASTIC_RULES = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.filters.elasticRulesTitle',
{
defaultMessage: 'Unknown',
defaultMessage: 'Elastic rules',
}
);
export const TAGS = i18n.translate('xpack.siem.detectionEngine.rules.allRules.filters.tagsLabel', {
defaultMessage: 'Tags',
});
export const NO_TAGS_AVAILABLE = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.filters.noTagsAvailableDescription',
{
defaultMessage: 'No tags available',
}
);
export const NO_RULES = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.filters.noRulesTitle',
{
defaultMessage: 'No rules found',
}
);
export const NO_RULES_BODY = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.filters.noRulesBodyTitle',
{
defaultMessage: "We weren't able to find any rules with the above filters.",
}
);

View file

@ -30,15 +30,9 @@ export interface TableData {
rule: {
href: string;
name: string;
status: string;
};
method: string;
risk_score: number;
severity: string;
lastCompletedRun: string | undefined;
lastResponse: {
type: string;
message?: string;
};
tags: string[];
activate: boolean;
isLoading: boolean;