Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] Detections add alert & signal tab #55127

Merged
merged 10 commits into from
Jan 18, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -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/detection_engine';

interface LinkToPageProps {
match: RouteMatch<{}>;
Expand Down Expand Up @@ -60,7 +61,7 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
<Route
component={RedirectToDetectionEnginePage}
exact
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`}
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/:tabName(${DetectionEngineTab.alert}|${DetectionEngineTab.signal})`}
strict
/>
<Route
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

import { DetectionEngineTab } from '../../pages/detection_engine/detection_engine';
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.signal;
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}`} />;
Expand All @@ -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 = ({
Expand All @@ -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.alert}`;
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`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,30 @@
* 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, useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';

import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';

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 { 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 { 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';
Expand All @@ -35,6 +38,12 @@ 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 { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine';

export enum DetectionEngineTab {
signal = 'signal',
XavierM marked this conversation as resolved.
Show resolved Hide resolved
alert = 'alert',
XavierM marked this conversation as resolved.
Show resolved Hide resolved
}

interface ReduxProps {
filters: esFilters.Filter[];
Expand All @@ -51,8 +60,23 @@ export interface DispatchProps {

type DetectionEngineComponentProps = ReduxProps & DispatchProps;

const detectionsTabs = [
{
id: DetectionEngineTab.signal,
name: i18n.SIGNAL,
disabled: false,
},
{
id: DetectionEngineTab.alert,
name: i18n.ALERT,
disabled: false,
},
];

const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
({ filters, query, setAbsoluteRangeDatePicker }) => {
const { tabName = DetectionEngineTab.signal } = useParams();
const [selectedTab, setSelectedTab] = useState(tabName);
const {
loading,
isSignalIndexExists,
Expand Down Expand Up @@ -87,6 +111,31 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
</WrapperPage>
);
}

useEffect(() => {
if (selectedTab !== tabName) {
setSelectedTab(tabName);
}
}, [selectedTab, setSelectedTab, tabName]);

const tabs = useMemo(
() => (
<EuiTabs>
{detectionsTabs.map(tab => (
<EuiTab
isSelected={tab.id === selectedTab}
disabled={tab.disabled}
key={tab.name}
href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[detectionsTabs, selectedTab]
);

return (
<>
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
Expand Down Expand Up @@ -117,26 +166,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}
/>
{selectedTab === DetectionEngineTab.signal && (
<>
<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}
/>
</>
)}
{selectedTab === DetectionEngineTab.alert && (
<>
<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>
Expand Down
15 changes: 11 additions & 4 deletions x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React from 'react';
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';

import { CreateRuleComponent } from './rules/create';
import { DetectionEngine } from './detection_engine';
import { DetectionEngine, DetectionEngineTab } from './detection_engine';
import { EditRuleComponent } from './rules/edit';
import { RuleDetails } from './rules/details';
import { RulesComponent } from './rules';
Expand All @@ -21,7 +21,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.signal}|${DetectionEngineTab.alert})`}
strict
>
<DetectionEngine />
</Route>
<Route exact path={`${detectionEnginePath}/rules`}>
Expand All @@ -30,7 +34,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`}>
Expand All @@ -39,7 +43,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.signal}${search}`}
/>
)}
/>
</Switch>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -187,17 +188,20 @@ 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.name}
XavierM marked this conversation as resolved.
Show resolved Hide resolved
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[ruleDetailTabs, ruleDetailTab, setRuleDetailTab]
);
const ruleError = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', {
defaultMessage: 'Signals',
});

export const ALERT = i18n.translate('xpack.siem.detectionEngine.signalTitle', {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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';
Expand All @@ -25,7 +25,7 @@ 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';
Expand All @@ -39,6 +39,7 @@ interface Props {
deleteQuery?: ({ id }: { id: string }) => void;
filters?: esFilters.Filter[];
from: number;
hideHeaderChildren?: boolean;
indexPattern: IIndexPattern;
query?: Query;
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
Expand All @@ -60,6 +61,7 @@ export const AlertsByCategory = React.memo<Props>(
deleteQuery,
filters = NO_FILTERS,
from,
hideHeaderChildren = false,
indexPattern,
query = DEFAULT_QUERY,
setAbsoluteRangeDatePicker,
Expand All @@ -76,9 +78,7 @@ 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>
),
[]
);
Expand Down Expand Up @@ -106,7 +106,7 @@ export const AlertsByCategory = React.memo<Props>(
queries: [query],
filters,
})}
headerChildren={alertsCountViewAlertsButton}
headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton}
id={ID}
isAlertsHistogram={true}
legendPosition={'right'}
Expand Down