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/types';

interface LinkToPageProps {
match: RouteMatch<{}>;
Expand Down Expand Up @@ -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
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/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}`} />;
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.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`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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 { 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 { 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 +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[];
Expand All @@ -51,8 +56,23 @@ 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 [selectedTab, setSelectedTab] = useState(tabName);
const {
loading,
isSignalIndexExists,
Expand Down Expand Up @@ -87,6 +107,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.id}
href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[detectionsTabs, selectedTab]
);

return (
<>
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
Expand All @@ -99,7 +144,6 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
</FiltersGlobal>
<WrapperPage>
<HeaderPage
border
subtitle={
lastSignals != null && (
<>
Expand All @@ -117,26 +161,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.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}
/>
</>
)}
{selectedTab === 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>
Expand Down
16 changes: 12 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 @@ -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)`;

Expand All @@ -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`}>
Expand All @@ -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`}>
Expand All @@ -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>
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 @@ -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,
},
Expand All @@ -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,
Expand Down Expand Up @@ -187,17 +193,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.id}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
),
[ruleDetailTabs, ruleDetailTab, setRuleDetailTab]
);
const ruleError = useMemo(
Expand Down Expand Up @@ -316,7 +325,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
{ruleError}
{tabs}
<EuiSpacer />
{ruleDetailTab === 'signal' && (
{ruleDetailTab === RuleDetailTabs.signals && (
<>
<EuiFlexGroup>
<EuiFlexItem component="section" grow={1}>
Expand Down Expand Up @@ -381,7 +390,9 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
)}
</>
)}
{ruleDetailTab === 'failure' && <FailureHistory id={rule?.id} />}
{ruleDetailTab === RuleDetailTabs.failures && (
<FailureHistory id={rule?.id} />
)}
</WrapperPage>
</StickyContainer>
)}
Expand Down
Loading