Skip to content

Commit

Permalink
[Detections Engine] Add Alert actions to the Timeline (#73228)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski authored Sep 1, 2020
1 parent 1ae2a14 commit b2be910
Show file tree
Hide file tree
Showing 75 changed files with 1,374 additions and 1,376 deletions.
6 changes: 5 additions & 1 deletion x-pack/plugins/security_solution/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EuiErrorBoundary } from '@elastic/eui';
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';

import { ManageUserInfo } from '../detections/components/user_info';
import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants';
import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher';
import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_capabilities_provider';
Expand All @@ -28,6 +29,7 @@ import { ManageGlobalTimeline } from '../timelines/components/manage_timeline';
import { StartServices } from '../types';
import { PageRouter } from './routes';
import { ManageSource } from '../common/containers/sourcerer';

interface StartAppComponent extends AppFrontendLibs {
children: React.ReactNode;
history: History;
Expand Down Expand Up @@ -57,7 +59,9 @@ const StartAppComponent: FC<StartAppComponent> = ({ children, apolloClient, hist
<ManageSource>
<ThemeProvider theme={theme}>
<MlCapabilitiesProvider>
<PageRouter history={history}>{children}</PageRouter>
<ManageUserInfo>
<PageRouter history={history}>{children}</PageRouter>
</ManageUserInfo>
</MlCapabilitiesProvider>
</ThemeProvider>
</ManageSource>
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/security_solution/public/app/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React, { useMemo } from 'react';
import styled from 'styled-components';

import { TimelineId } from '../../../common/types/timeline';
import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper';
import { Flyout } from '../../timelines/components/flyout';
import { HeaderGlobal } from '../../common/components/header_global';
Expand All @@ -17,6 +18,7 @@ import { useWithSource } from '../../common/containers/source';
import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline';
import { navTabs } from './home_navigations';
import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
import { useUserInfo } from '../../detections/components/user_info';

const SecuritySolutionAppWrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -52,6 +54,9 @@ const HomePageComponent: React.FC<HomePageProps> = ({ children }) => {
const [showTimeline] = useShowTimeline();
const { browserFields, indexPattern, indicesExist } = useWithSource('default', indexToAdd);

// side effect: this will attempt to create the signals index if it doesn't exist
useUserInfo();

return (
<SecuritySolutionAppWrapper>
<HeaderGlobal />
Expand All @@ -62,7 +67,7 @@ const HomePageComponent: React.FC<HomePageProps> = ({ children }) => {
{indicesExist && showTimeline && (
<>
<AutoSaveWarningMsg />
<Flyout timelineId="timeline-1" usersViewing={usersViewing} />
<Flyout timelineId={TimelineId.active} usersViewing={usersViewing} />
</>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
*/

import React, { useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';

import { Filter } from '../../../../../../../src/plugins/data/public';
import { TimelineIdLiteral } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../events_viewer';
import { alertsDefaultModel } from './default_headers';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import * as i18n from './translations';
import { useKibana } from '../../lib/kibana';

Expand Down Expand Up @@ -68,7 +66,6 @@ const AlertsTableComponent: React.FC<Props> = ({
startDate,
pageFilters = [],
}) => {
const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { filterManager } = useKibana().services.data.query;
const { initializeTimeline } = useManageTimeline();
Expand All @@ -80,12 +77,12 @@ const AlertsTableComponent: React.FC<Props> = ({
filterManager,
defaultModel: alertsDefaultModel,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })],
title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<StatefulEventsViewer
pageFilters={alertsFilter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import * as i18n from './translations';
import { MatrixHistogramOption, MatrixHisrogramConfigs } from '../matrix_histogram/types';
import { MatrixHistogramOption, MatrixHistogramConfigs } from '../matrix_histogram/types';
import { HistogramType } from '../../../graphql/types';

export const alertsStackByOptions: MatrixHistogramOption[] = [
Expand All @@ -21,7 +21,7 @@ export const alertsStackByOptions: MatrixHistogramOption[] = [

const DEFAULT_STACK_BY = 'event.module';

export const histogramConfigs: MatrixHisrogramConfigs = {
export const histogramConfigs: MatrixHistogramConfigs = {
defaultStackByOption:
alertsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[1],
errorMessage: i18n.ERROR_FETCHING_ALERTS_DATA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as i18n from './translations';
import { useUiSetting$ } from '../../lib/kibana';
import { MatrixHistogramContainer } from '../matrix_histogram';
import { histogramConfigs } from './histogram_configs';
import { MatrixHisrogramConfigs } from '../matrix_histogram/types';
import { MatrixHistogramConfigs } from '../matrix_histogram/types';
const ID = 'alertsOverTimeQuery';

export const AlertsView = ({
Expand All @@ -38,7 +38,7 @@ export const AlertsView = ({
[]
);
const { globalFullScreen } = useFullScreen();
const alertsHistogramConfigs: MatrixHisrogramConfigs = useMemo(
const alertsHistogramConfigs: MatrixHistogramConfigs = useMemo(
() => ({
...histogramConfigs,
subtitle: getSubtitle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
<div data-test-subj="mock-utility-bar" />
);

const exceptionsModal = (refetch: inputsModel.Refetch) => (
<div data-test-subj="mock-exceptions-modal" />
);

const eventsViewerDefaultProps = {
browserFields: {},
columns: [],
Expand Down Expand Up @@ -464,42 +460,4 @@ describe('EventsViewer', () => {
});
});
});

describe('exceptions modal', () => {
test('it renders exception modal if "exceptionsModal" callback exists', async () => {
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mockEventViewerResponse} addTypename={false}>
<EventsViewer
{...eventsViewerDefaultProps}
exceptionsModal={exceptionsModal}
graphEventId=""
/>
</MockedProvider>
</TestProviders>
);

await waitFor(() => {
wrapper.update();

expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeTruthy();
});
});

test('it does not render exception modal if "exceptionModal" callback does not exist', async () => {
const wrapper = mount(
<TestProviders>
<MockedProvider mocks={mockEventViewerResponse} addTypename={false}>
<EventsViewer {...eventsViewerDefaultProps} graphEventId="" />
</MockedProvider>
</TestProviders>
);

await waitFor(() => {
wrapper.update();

expect(wrapper.find(`[data-test-subj="mock-exceptions-modal"]`).exists()).toBeFalsy();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ interface Props {
utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode;
// If truthy, the graph viewer (Resolver) is showing
graphEventId: string | undefined;
exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode;
}

const EventsViewerComponent: React.FC<Props> = ({
Expand All @@ -135,7 +134,6 @@ const EventsViewerComponent: React.FC<Props> = ({
toggleColumn,
utilityBar,
graphEventId,
exceptionsModal,
}) => {
const { globalFullScreen } = useFullScreen();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
Expand Down Expand Up @@ -261,7 +259,6 @@ const EventsViewerComponent: React.FC<Props> = ({
</HeaderFilterGroupWrapper>
)}
</HeaderSection>
{exceptionsModal && exceptionsModal(refetch)}
{utilityBar && !resolverIsShowing(graphEventId) && (
<UtilityBar>{utilityBar?.(refetch, totalCountMinusDeleted)}</UtilityBar>
)}
Expand All @@ -280,6 +277,7 @@ const EventsViewerComponent: React.FC<Props> = ({
docValueFields={docValueFields}
id={id}
isEventViewer={true}
refetch={refetch}
sort={sort}
toggleColumn={toggleColumn}
/>
Expand Down Expand Up @@ -338,6 +336,5 @@ export const EventsViewer = React.memo(
prevProps.start === nextProps.start &&
prevProps.sort === nextProps.sort &&
prevProps.utilityBar === nextProps.utilityBar &&
prevProps.graphEventId === nextProps.graphEventId &&
prevProps.exceptionsModal === nextProps.exceptionsModal
prevProps.graphEventId === nextProps.graphEventId
);
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export interface OwnProps {
headerFilterGroup?: React.ReactNode;
pageFilters?: Filter[];
utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode;
exceptionsModal?: (refetch: inputsModel.Refetch) => React.ReactNode;
}

type Props = OwnProps & PropsFromRedux;
Expand Down Expand Up @@ -75,7 +74,6 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
utilityBar,
// If truthy, the graph viewer (Resolver) is showing
graphEventId,
exceptionsModal,
}) => {
const [
{ docValueFields, browserFields, indexPatterns, isLoading: isLoadingIndexPattern },
Expand Down Expand Up @@ -158,7 +156,6 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
toggleColumn={toggleColumn}
utilityBar={utilityBar}
graphEventId={graphEventId}
exceptionsModal={exceptionsModal}
/>
</InspectButtonContainer>
</FullScreenContainer>
Expand Down Expand Up @@ -223,7 +220,6 @@ type PropsFromRedux = ConnectedProps<typeof connector>;
export const StatefulEventsViewer = connector(
React.memo(
StatefulEventsViewerComponent,
// eslint-disable-next-line complexity
(prevProps, nextProps) =>
prevProps.id === nextProps.id &&
deepEqual(prevProps.columns, nextProps.columns) &&
Expand All @@ -244,7 +240,6 @@ export const StatefulEventsViewer = connector(
prevProps.showCheckboxes === nextProps.showCheckboxes &&
prevProps.start === nextProps.start &&
prevProps.utilityBar === nextProps.utilityBar &&
prevProps.graphEventId === nextProps.graphEventId &&
prevProps.exceptionsModal === nextProps.exceptionsModal
prevProps.graphEventId === nextProps.graphEventId
)
);
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface AddExceptionModalBaseProps {

export interface AddExceptionModalProps extends AddExceptionModalBaseProps {
onCancel: () => void;
onConfirm: (didCloseAlert: boolean) => void;
onConfirm: (didCloseAlert: boolean, didBulkCloseAlert: boolean) => void;
onRuleChange?: () => void;
alertStatus?: Status;
}
Expand Down Expand Up @@ -137,8 +137,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({
);
const onSuccess = useCallback(() => {
addSuccess(i18n.ADD_EXCEPTION_SUCCESS);
onConfirm(shouldCloseAlert);
}, [addSuccess, onConfirm, shouldCloseAlert]);
onConfirm(shouldCloseAlert, shouldBulkCloseAlert);
}, [addSuccess, onConfirm, shouldBulkCloseAlert, shouldCloseAlert]);

const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface MatrixHistogramOption {
export type GetSubTitle = (count: number) => string;
export type GetTitle = (matrixHistogramOption: MatrixHistogramOption) => string;

export interface MatrixHisrogramConfigs {
export interface MatrixHistogramConfigs {
defaultStackByOption: MatrixHistogramOption;
errorMessage: string;
hideHistogramIfEmpty?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as H from 'history';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
import { url } from '../../../../../../../src/plugins/kibana_utils/public';

import { TimelineId } from '../../../../common/types/timeline';
import { SecurityPageName } from '../../../app/types';
import { inputsSelectors, State } from '../../store';
import { UrlInputsModel } from '../../store/inputs/model';
Expand Down Expand Up @@ -122,7 +123,7 @@ export const makeMapStateToProps = () => {
const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global;
const { linkTo: timelineLinkTo, timerange: timelineTimerange } = inputState.timeline;

const flyoutTimeline = getTimeline(state, 'timeline-1');
const flyoutTimeline = getTimeline(state, TimelineId.active);
const timeline =
flyoutTimeline != null
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import * as i18n from './translations';
import {
MatrixHistogramOption,
MatrixHisrogramConfigs,
MatrixHistogramConfigs,
} from '../../../components/matrix_histogram/types';
import { HistogramType } from '../../../../graphql/types';

Expand All @@ -19,7 +19,7 @@ export const anomaliesStackByOptions: MatrixHistogramOption[] = [

const DEFAULT_STACK_BY = i18n.ANOMALIES_STACK_BY_JOB_ID;

export const histogramConfigs: MatrixHisrogramConfigs = {
export const histogramConfigs: MatrixHistogramConfigs = {
defaultStackByOption:
anomaliesStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? anomaliesStackByOptions[0],
errorMessage: i18n.ERROR_FETCHING_ANOMALIES_DATA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { FilterStateStore } from '../../../../../../src/plugins/data/common/es_query/filters/meta_filter';

import { TimelineType, TimelineStatus } from '../../../common/types/timeline';
import { TimelineId, TimelineType, TimelineStatus } from '../../../common/types/timeline';

import { OpenTimelineResult } from '../../timelines/components/open_timeline/types';
import {
Expand Down Expand Up @@ -2227,7 +2227,7 @@ export const defaultTimelineProps: CreateTimelineProps = {
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
id: 'timeline-1',
id: TimelineId.active,
isFavorite: false,
isLive: false,
isLoading: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../../../common/mock/';
import { CreateTimeline, UpdateTimelineLoading } from './types';
import { Ecs } from '../../../graphql/types';
import { TimelineType, TimelineStatus } from '../../../../common/types/timeline';
import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline';

jest.mock('apollo-client');

Expand Down Expand Up @@ -67,7 +67,10 @@ describe('alert actions', () => {
});

expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1);
expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true });
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: true,
});
});

test('it invokes createTimeline with designated timeline template if "timelineTemplate" exists', async () => {
Expand Down Expand Up @@ -313,9 +316,12 @@ describe('alert actions', () => {
updateTimelineIsLoading,
});

expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true });
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: 'timeline-1',
id: TimelineId.active,
isLoading: true,
});
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
id: TimelineId.active,
isLoading: false,
});
expect(createTimeline).toHaveBeenCalledTimes(1);
Expand Down
Loading

0 comments on commit b2be910

Please sign in to comment.