[SIEM] Detections add alert & signal tab (#55127) (#55263)

* add alert on detections

* review I + fix unit test

* review II

* review III

* review IV + bug fixes found during review

* review VI
This commit is contained in:
Xavier Mouligneau 2020-01-17 22:12:41 -05:00 committed by GitHub
parent f411d2f65e
commit c62349689e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 241 additions and 142 deletions

View file

@ -16,11 +16,11 @@ import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index
const ID = 'alertsOverTimeQuery';
export const alertsStackByOptions: MatrixHistogramOption[] = [
{
text: i18n.CATEGORY,
text: 'event.category',
value: 'event.category',
},
{
text: i18n.MODULE,
text: 'event.module',
value: 'event.module',
},
];
@ -54,7 +54,6 @@ export const AlertsView = ({
<>
<MatrixHistogramContainer
dataKey={dataKey}
deleteQuery={deleteQuery}
defaultStackByOption={alertsStackByOptions[1]}
endDate={endDate}
errorMessage={i18n.ERROR_FETCHING_ALERTS_DATA}
@ -68,7 +67,7 @@ export const AlertsView = ({
stackByOptions={alertsStackByOptions}
startDate={startDate}
subtitle={getSubtitle}
title={i18n.ALERTS_DOCUMENT_TYPE}
title={i18n.ALERTS_GRAPH_TITLE}
type={type}
updateDateRange={updateDateRange}
/>

View file

@ -14,10 +14,14 @@ export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.alertsView.total
defaultMessage: 'alerts match the search criteria',
});
export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsDocumentType', {
export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsTableTitle', {
defaultMessage: 'Alerts',
});
export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.alertsView.alertsGraphTitle', {
defaultMessage: 'Alert detection frequency',
});
export const ALERTS_STACK_BY_MODULE = i18n.translate(
'xpack.siem.alertsView.alertsStackByOptions.module',
{

View file

@ -20,6 +20,7 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho
import { RedirectToNetworkPage } from './redirect_to_network';
import { RedirectToOverviewPage } from './redirect_to_overview';
import { RedirectToTimelinesPage } from './redirect_to_timelines';
import { DetectionEngineTab } from '../../pages/detection_engine/types';
interface LinkToPageProps {
match: RouteMatch<{}>;
@ -63,6 +64,12 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`}
strict
/>
<Route
component={RedirectToDetectionEnginePage}
exact
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/:tabName(${DetectionEngineTab.alerts}|${DetectionEngineTab.signals})`}
strict
/>
<Route
component={RedirectToRulesPage}
exact

View file

@ -7,19 +7,28 @@
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { DetectionEngineTab } from '../../pages/detection_engine/types';
import { RedirectWrapper } from './redirect_wrapper';
export type DetectionEngineComponentProps = RouteComponentProps<{
tabName: DetectionEngineTab;
search: string;
}>;
export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine';
export const RedirectToDetectionEnginePage = ({
match: {
params: { tabName },
},
location: { search },
}: DetectionEngineComponentProps) => (
<RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}${search}`} />
);
}: DetectionEngineComponentProps) => {
const defaultSelectedTab = DetectionEngineTab.signals;
const selectedTab = tabName ? tabName : defaultSelectedTab;
const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`;
return <RedirectWrapper to={to} />;
};
export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => {
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules${search}`} />;
@ -28,7 +37,7 @@ export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineCom
export const RedirectToCreateRulePage = ({
location: { search },
}: DetectionEngineComponentProps) => {
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule${search}`} />;
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/create${search}`} />;
};
export const RedirectToRuleDetailsPage = ({
@ -44,6 +53,8 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine
};
export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`;
export const getDetectionEngineAlertUrl = () =>
`#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`;
export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`;
export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`;
export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`;

View file

@ -46,12 +46,12 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
isDnsHistogram,
isEventsHistogram,
isInspected,
legendPosition,
legendPosition = 'right',
mapping,
query,
scaleType = ScaleType.Time,
setQuery,
showLegend,
showLegend = true,
skip,
stackByOptions,
startDate,
@ -151,6 +151,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
isInspected,
loading,
data,
refetch,
]);
return !hideHistogram ? (

View file

@ -39,6 +39,14 @@ export const AnomaliesQueryTabBody = ({
flowTarget,
ip,
}: AnomaliesQueryTabBodyProps) => {
useEffect(() => {
return () => {
if (deleteQuery) {
deleteQuery({ id: ID });
}
};
}, []);
const [, siemJobs] = useSiemJobs(true);
const [anomalyScore] = useUiSetting$<number>(DEFAULT_ANOMALY_SCORE);
@ -51,21 +59,12 @@ export const AnomaliesQueryTabBody = ({
ip
);
useEffect(() => {
return () => {
if (deleteQuery) {
deleteQuery({ id: ID });
}
};
}, []);
return (
<>
<MatrixHistogramContainer
isAnomaliesHistogram={true}
dataKey="AnomaliesHistogram"
defaultStackByOption={anomaliesStackByOptions[0]}
deleteQuery={deleteQuery}
endDate={endDate}
errorMessage={i18n.ERROR_FETCHING_ANOMALIES_DATA}
filterQuery={mergedFilterQuery}

View file

@ -26,7 +26,6 @@ import { SetQuery } from '../../pages/hosts/navigation/types';
export interface OwnProps extends QueryTemplateProps {
dataKey: string | string[];
defaultStackByOption: MatrixHistogramOption;
deleteQuery?: ({ id }: { id: string }) => void;
errorMessage: string;
headerChildren?: React.ReactNode;
hideHistogramIfEmpty?: boolean;

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { getOr } from 'lodash/fp';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
MatrixHistogramDataTypes,
MatrixHistogramQueryProps,
@ -35,7 +35,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
}: MatrixHistogramQueryProps) => {
const [defaultIndex] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
const [, dispatchToaster] = useStateToaster();
const [refetch, setRefetch] = useState<inputsModel.Refetch>();
const refetch = useRef<inputsModel.Refetch>();
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<MatrixHistogramDataTypes[] | null>(null);
const [inspect, setInspect] = useState<inputsModel.InspectQuery | null>(null);
@ -71,7 +71,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
return apolloClient
.query<GetMatrixHistogramQuery.Query, GetMatrixHistogramQuery.Variables>({
query,
fetchPolicy: 'cache-first',
fetchPolicy: 'network-only',
variables: matrixHistogramVariables,
context: {
fetchOptions: {
@ -103,9 +103,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
}
);
}
setRefetch(() => {
fetchData();
});
refetch.current = fetchData;
fetchData();
return () => {
isSubscribed = false;
@ -122,5 +120,5 @@ export const useQuery = <Hit, Aggs, TCache = object>({
endDate,
]);
return { data, loading, inspect, totalCount, refetch };
return { data, loading, inspect, totalCount, refetch: refetch.current };
};

View file

@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle',
});
export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', {
defaultMessage: 'All signals',
defaultMessage: 'Signals',
});
export const SIGNALS_DOCUMENT_TYPE = i18n.translate(

View file

@ -4,18 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
import * as i18n from './translations';
import { SignalsHistogramOption } from './types';
export const signalsHistogramOptions: SignalsHistogramOption[] = [
{ text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' },
{ text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' },
{ text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' },
{ text: i18n.STACK_BY_ACTIONS, value: 'event.action' },
{ text: i18n.STACK_BY_CATEGORIES, value: 'event.category' },
{ text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' },
{ text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' },
{ text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' },
{ text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' },
{ text: i18n.STACK_BY_USERS, value: 'user.name' },
{ text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' },
{ text: 'signal.rule.severity', value: 'signal.rule.severity' },
{ text: 'destination.ip', value: 'destination.ip' },
{ text: 'event.action', value: 'event.action' },
{ text: 'event.category', value: 'event.category' },
{ text: 'host.name', value: 'host.name' },
{ text: 'signal.rule.type', value: 'signal.rule.type' },
{ text: 'signal.rule.name', value: 'signal.rule.name' },
{ text: 'source.ip', value: 'source.ip' },
{ text: 'user.name', value: 'user.name' },
];

View file

@ -46,7 +46,7 @@ export const SignalsHistogramPanel = memo<SignalsHistogramPanelProps>(
filters,
query,
from,
legendPosition = 'bottom',
legendPosition = 'right',
loadingInitial = false,
showLinkToSignals = false,
showTotalSignalsCount = false,

View file

@ -44,7 +44,7 @@ export const SignalsHistogram = React.memo<HistogramSignalsProps>(
from,
query,
filters,
legendPosition = 'bottom',
legendPosition = 'right',
loadingInitial,
setTotalSignalsCount,
stackByField,

View file

@ -4,27 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButton, EuiSpacer } from '@elastic/eui';
import React, { useCallback } from 'react';
import { EuiButton, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import { FiltersGlobal } from '../../components/filters_global';
import { HeaderPage } from '../../components/header_page';
import { SiemSearchBar } from '../../components/search_bar';
import { WrapperPage } from '../../components/wrapper_page';
import { GlobalTime } from '../../containers/global_time';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
import { SpyRoute } from '../../utils/route/spy_routes';
import { Query } from '../../../../../../../src/plugins/data/common/query';
import { esFilters } from '../../../../../../../src/plugins/data/common/es_query';
import { GlobalTime } from '../../containers/global_time';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
import { AlertsTable } from '../../components/alerts_viewer/alerts_table';
import { FiltersGlobal } from '../../components/filters_global';
import { HeaderPage } from '../../components/header_page';
import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine';
import { SiemSearchBar } from '../../components/search_bar';
import { WrapperPage } from '../../components/wrapper_page';
import { State } from '../../store';
import { inputsSelectors } from '../../store/inputs';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { SpyRoute } from '../../utils/route/spy_routes';
import { InputsModelId } from '../../store/inputs/constants';
import { InputsRange } from '../../store/inputs/model';
import { AlertsByCategory } from '../overview/alerts_by_category';
import { useSignalInfo } from './components/signals_info';
import { SignalsTable } from './components/signals';
import { NoWriteSignalsCallOut } from './components/no_write_signals_callout';
@ -35,6 +39,7 @@ import { DetectionEngineEmptyPage } from './detection_engine_empty_page';
import { DetectionEngineNoIndex } from './detection_engine_no_signal_index';
import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated';
import * as i18n from './translations';
import { DetectionEngineTab } from './types';
interface ReduxProps {
filters: esFilters.Filter[];
@ -51,8 +56,22 @@ export interface DispatchProps {
type DetectionEngineComponentProps = ReduxProps & DispatchProps;
const detectionsTabs = [
{
id: DetectionEngineTab.signals,
name: i18n.SIGNAL,
disabled: false,
},
{
id: DetectionEngineTab.alerts,
name: i18n.ALERT,
disabled: false,
},
];
const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
({ filters, query, setAbsoluteRangeDatePicker }) => {
const { tabName = DetectionEngineTab.signals } = useParams();
const {
loading,
isSignalIndexExists,
@ -87,6 +106,25 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
</WrapperPage>
);
}
const tabs = useMemo(
() => (
<EuiTabs>
{detectionsTabs.map(tab => (
<EuiTab
isSelected={tab.id === tabName}
disabled={tab.disabled}
key={tab.id}
href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[detectionsTabs, tabName]
);
return (
<>
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
@ -99,7 +137,6 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
</FiltersGlobal>
<WrapperPage>
<HeaderPage
border
subtitle={
lastSignals != null && (
<>
@ -117,26 +154,49 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
</HeaderPage>
<GlobalTime>
{({ to, from }) => (
{({ to, from, deleteQuery, setQuery }) => (
<>
<SignalsHistogramPanel
filters={filters}
from={from}
loadingInitial={loading}
query={query}
stackByOptions={signalsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
{tabs}
<EuiSpacer />
<SignalsTable
loading={loading}
hasIndexWrite={hasIndexWrite ?? false}
canUserCRUD={canUserCRUD ?? false}
from={from}
signalsIndex={signalIndexName ?? ''}
to={to}
/>
{tabName === DetectionEngineTab.signals && (
<>
<SignalsHistogramPanel
filters={filters}
from={from}
loadingInitial={loading}
query={query}
stackByOptions={signalsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
<EuiSpacer size="l" />
<SignalsTable
loading={loading}
hasIndexWrite={hasIndexWrite ?? false}
canUserCRUD={canUserCRUD ?? false}
from={from}
signalsIndex={signalIndexName ?? ''}
to={to}
/>
</>
)}
{tabName === DetectionEngineTab.alerts && (
<>
<AlertsByCategory
deleteQuery={deleteQuery}
filters={filters}
from={from}
hideHeaderChildren={true}
indexPattern={indexPattern}
query={query}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker!}
setQuery={setQuery}
to={to}
/>
<EuiSpacer size="l" />
<AlertsTable endDate={to} startDate={from} />
</>
)}
</>
)}
</GlobalTime>

View file

@ -7,12 +7,13 @@
import React from 'react';
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
import { ManageUserInfo } from './components/user_info';
import { CreateRuleComponent } from './rules/create';
import { DetectionEngine } from './detection_engine';
import { EditRuleComponent } from './rules/edit';
import { RuleDetails } from './rules/details';
import { RulesComponent } from './rules';
import { ManageUserInfo } from './components/user_info';
import { DetectionEngineTab } from './types';
const detectionEnginePath = `/:pageName(detection-engine)`;
@ -21,7 +22,11 @@ type Props = Partial<RouteComponentProps<{}>> & { url: string };
export const DetectionEngineContainer = React.memo<Props>(() => (
<ManageUserInfo>
<Switch>
<Route exact path={detectionEnginePath} strict>
<Route
exact
path={`${detectionEnginePath}/:tabName(${DetectionEngineTab.signals}|${DetectionEngineTab.alerts})`}
strict
>
<DetectionEngine />
</Route>
<Route exact path={`${detectionEnginePath}/rules`}>
@ -30,7 +35,7 @@ export const DetectionEngineContainer = React.memo<Props>(() => (
<Route exact path={`${detectionEnginePath}/rules/create`}>
<CreateRuleComponent />
</Route>
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId`}>
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId/`}>
<RuleDetails />
</Route>
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId/edit`}>
@ -39,7 +44,10 @@ export const DetectionEngineContainer = React.memo<Props>(() => (
<Route
path="/detection-engine/"
render={({ location: { search = '' } }) => (
<Redirect from="/detection-engine/" to={`/detection-engine${search}`} />
<Redirect
from="/detection-engine/"
to={`/detection-engine/${DetectionEngineTab.signals}${search}`}
/>
)}
/>
</Switch>

View file

@ -12,6 +12,7 @@ import {
EuiSpacer,
EuiHealth,
EuiTab,
EuiTabs,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { memo, useCallback, useMemo, useState } from 'react';
@ -78,14 +79,19 @@ export interface DispatchProps {
}>;
}
enum RuleDetailTabs {
signals = 'signals',
failures = 'failures',
}
const ruleDetailTabs = [
{
id: 'signal',
id: RuleDetailTabs.signals,
name: detectionI18n.SIGNAL,
disabled: false,
},
{
id: 'failure',
id: RuleDetailTabs.failures,
name: i18n.FAILURE_HISTORY_TAB,
disabled: false,
},
@ -106,7 +112,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
} = useUserInfo();
const { ruleId } = useParams();
const [isLoading, rule] = useRule(ruleId);
const [ruleDetailTab, setRuleDetailTab] = useState('signal');
const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals);
const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({
rule,
detailsView: true,
@ -187,22 +193,27 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
: 'subdued';
const tabs = useMemo(
() =>
ruleDetailTabs.map(tab => (
<EuiTab
onClick={() => setRuleDetailTab(tab.id)}
isSelected={tab.id === ruleDetailTab}
disabled={tab.disabled}
key={tab.name}
>
{tab.name}
</EuiTab>
)),
() => (
<EuiTabs>
{ruleDetailTabs.map(tab => (
<EuiTab
onClick={() => setRuleDetailTab(tab.id)}
isSelected={tab.id === ruleDetailTab}
disabled={tab.disabled}
key={tab.id}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[ruleDetailTabs, ruleDetailTab, setRuleDetailTab]
);
const ruleError = useMemo(
() =>
rule?.status === 'failed' && ruleDetailTab === 'signal' && rule?.last_failure_at != null ? (
rule?.status === 'failed' &&
ruleDetailTab === RuleDetailTabs.signals &&
rule?.last_failure_at != null ? (
<RuleStatusFailedCallOut
message={rule?.last_failure_message ?? ''}
date={rule?.last_failure_at}
@ -316,7 +327,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
{ruleError}
{tabs}
<EuiSpacer />
{ruleDetailTab === 'signal' && (
{ruleDetailTab === RuleDetailTabs.signals && (
<>
<EuiFlexGroup>
<EuiFlexItem component="section" grow={1}>
@ -381,7 +392,9 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
)}
</>
)}
{ruleDetailTab === 'failure' && <FailureHistory id={rule?.id} />}
{ruleDetailTab === RuleDetailTabs.failures && (
<FailureHistory id={rule?.id} />
)}
</WrapperPage>
</StickyContainer>
)}

View file

@ -22,7 +22,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add
});
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', {
defaultMessage: 'Rules',
defaultMessage: 'Signal detection rules',
});
export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', {

View file

@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', {
defaultMessage: 'Signals',
});
export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', {
defaultMessage: 'Third-party alerts',
});
export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', {
defaultMessage: 'Manage rules',
defaultMessage: 'Manage signal detection rules',
});
export const PANEL_SUBTITLE_SHOWING = i18n.translate(

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export enum DetectionEngineTab {
signals = 'signals',
alerts = 'alerts',
}

View file

@ -25,7 +25,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable);
const ID = 'authenticationsOverTimeQuery';
const authStackByOptions: MatrixHistogramOption[] = [
{
text: i18n.NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE,
text: 'event.type',
value: 'event.type',
},
];
@ -71,7 +71,6 @@ export const AuthenticationsQueryTabBody = ({
isAuthenticationsHistogram={true}
dataKey="AuthenticationsHistogram"
defaultStackByOption={authStackByOptions[0]}
deleteQuery={deleteQuery}
endDate={endDate}
errorMessage={i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA}
filterQuery={filterQuery}

View file

@ -20,11 +20,11 @@ const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
export const eventsStackByOptions: MatrixHistogramOption[] = [
{
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION,
text: 'event.action',
value: 'event.action',
},
{
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET,
text: 'event.dataset',
value: 'event.dataset',
},
];
@ -50,7 +50,6 @@ export const EventsQueryTabBody = ({
<MatrixHistogramContainer
dataKey="EventsHistogram"
defaultStackByOption={eventsStackByOptions[0]}
deleteQuery={deleteQuery}
endDate={endDate}
isEventsHistogram={true}
errorMessage={i18n.ERROR_FETCHING_EVENTS_DATA}

View file

@ -28,13 +28,6 @@ export const NAVIGATION_AUTHENTICATIONS_TITLE = i18n.translate(
}
);
export const NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE = i18n.translate(
'xpack.siem.hosts.navigation.authentications.stackByEventType',
{
defaultMessage: 'event type',
}
);
export const NAVIGATION_UNCOMMON_PROCESSES_TITLE = i18n.translate(
'xpack.siem.hosts.navigation.uncommonProcessesTitle',
{
@ -53,20 +46,6 @@ export const NAVIGATION_EVENTS_TITLE = i18n.translate('xpack.siem.hosts.navigati
defaultMessage: 'Events',
});
export const NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION = i18n.translate(
'xpack.siem.hosts.navigation.eventsStackByEventAction',
{
defaultMessage: 'action',
}
);
export const NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET = i18n.translate(
'xpack.siem.hosts.navigation.eventsStackByEventDataset',
{
defaultMessage: 'dataset',
}
);
export const NAVIGATION_ALERTS_TITLE = i18n.translate('xpack.siem.hosts.navigation.alertsTitle', {
defaultMessage: 'Alerts',
});

View file

@ -24,7 +24,7 @@ const NetworkDnsTableManage = manageQuery(NetworkDnsTable);
const dnsStackByOptions: MatrixHistogramOption[] = [
{
text: i18n.STACK_BY_DOMAIN,
text: 'dns.question.registered_domain',
value: 'dns.question.registered_domain',
},
];
@ -70,7 +70,6 @@ export const DnsQueryTabBody = ({
title={getTitle}
type={networkModel.NetworkType.page}
updateDateRange={updateDateRange}
showLegend={false}
/>
<EuiSpacer />
<NetworkDnsQuery

View file

@ -22,10 +22,6 @@ export const NAVIGATION_DNS_TITLE = i18n.translate('xpack.siem.network.navigatio
defaultMessage: 'DNS',
});
export const STACK_BY_DOMAIN = i18n.translate('xpack.siem.hosts.dns.stackByDomain', {
defaultMessage: 'unique domains',
});
export const ERROR_FETCHING_DNS_DATA = i18n.translate(
'xpack.siem.hosts.navigation.dns.histogram.errorFetchingDnsData',
{

View file

@ -6,7 +6,7 @@
import { EuiButton } from '@elastic/eui';
import numeral from '@elastic/numeral';
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
import styled from 'styled-components';
@ -16,16 +16,15 @@ import {
UNIT,
} from '../../../components/alerts_viewer/translations';
import { alertsStackByOptions } from '../../../components/alerts_viewer';
import { getTabsOnHostsUrl } from '../../../components/link_to/redirect_to_hosts';
import { getDetectionEngineAlertUrl } from '../../../components/link_to/redirect_to_detection_engine';
import { MatrixHistogramContainer } from '../../../containers/matrix_histogram';
import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/index.gql_query';
import { MatrixHistogramOption } from '../../../components/matrix_histogram/types';
import { useKibana, useUiSetting$ } from '../../../lib/kibana';
import { convertToBuildEsQuery } from '../../../lib/keury';
import { SetAbsoluteRangeDatePicker } from '../../network/types';
import { esQuery } from '../../../../../../../../src/plugins/data/public';
import { inputsModel } from '../../../store';
import { HostsTableType, HostsType } from '../../../store/hosts/model';
import { HostsType } from '../../../store/hosts/model';
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
import * as i18n from '../translations';
@ -39,6 +38,7 @@ interface Props {
deleteQuery?: ({ id }: { id: string }) => void;
filters?: esFilters.Filter[];
from: number;
hideHeaderChildren?: boolean;
indexPattern: IIndexPattern;
query?: Query;
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
@ -60,14 +60,24 @@ export const AlertsByCategory = React.memo<Props>(
deleteQuery,
filters = NO_FILTERS,
from,
hideHeaderChildren = false,
indexPattern,
query = DEFAULT_QUERY,
setAbsoluteRangeDatePicker,
setQuery,
to,
}) => {
useEffect(() => {
return () => {
if (deleteQuery) {
deleteQuery({ id: ID });
}
};
}, []);
const kibana = useKibana();
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
const updateDateRangeCallback = useCallback(
(min: number, max: number) => {
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
@ -76,17 +86,11 @@ export const AlertsByCategory = React.memo<Props>(
);
const alertsCountViewAlertsButton = useMemo(
() => (
<ViewAlertsButton href={getTabsOnHostsUrl(HostsTableType.alerts)}>
{i18n.VIEW_ALERTS}
</ViewAlertsButton>
<ViewAlertsButton href={getDetectionEngineAlertUrl()}>{i18n.VIEW_ALERTS}</ViewAlertsButton>
),
[]
);
const getTitle = useCallback(
(option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text),
[]
);
const getSubtitle = useCallback(
(totalCount: number) =>
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
@ -96,7 +100,6 @@ export const AlertsByCategory = React.memo<Props>(
return (
<MatrixHistogramContainer
dataKey="AlertsHistogram"
deleteQuery={deleteQuery}
defaultStackByOption={alertsStackByOptions[0]}
endDate={to}
errorMessage={ERROR_FETCHING_ALERTS_DATA}
@ -106,7 +109,7 @@ export const AlertsByCategory = React.memo<Props>(
queries: [query],
filters,
})}
headerChildren={alertsCountViewAlertsButton}
headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton}
id={ID}
isAlertsHistogram={true}
legendPosition={'right'}
@ -115,7 +118,7 @@ export const AlertsByCategory = React.memo<Props>(
sourceId="default"
stackByOptions={alertsStackByOptions}
startDate={from}
title={getTitle}
title={i18n.ALERTS_GRAPH_TITLE}
subtitle={getSubtitle}
type={HostsType.page}
updateDateRange={updateDateRangeCallback}

View file

@ -6,7 +6,7 @@
import { EuiButton } from '@elastic/eui';
import numeral from '@elastic/numeral';
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
import styled from 'styled-components';
@ -66,8 +66,17 @@ export const EventsByDataset = React.memo<Props>(
setQuery,
to,
}) => {
useEffect(() => {
return () => {
if (deleteQuery) {
deleteQuery({ id: ID });
}
};
}, []);
const kibana = useKibana();
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
const updateDateRangeCallback = useCallback(
(min: number, max: number) => {
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
@ -96,7 +105,6 @@ export const EventsByDataset = React.memo<Props>(
return (
<MatrixHistogramContainer
dataKey="EventsHistogram"
deleteQuery={deleteQuery}
defaultStackByOption={eventsStackByOptions[1]}
endDate={to}
errorMessage={ERROR_FETCHING_EVENTS_DATA}

View file

@ -12,6 +12,10 @@ export const ALERTS_COUNT_BY = (groupByField: string) =>
defaultMessage: 'Alerts count by {groupByField}',
});
export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', {
defaultMessage: 'Alert detection frequency',
});
export const EVENTS_COUNT_BY = (groupByField: string) =>
i18n.translate('xpack.siem.overview.eventsCountByTitle', {
values: { groupByField },