Skip to content

Commit

Permalink
[Security Solution] Add more actions to alerts flyout (#105767)
Browse files Browse the repository at this point in the history
* add investigate in timeline action to flyout

* close context menu on item clicked

* add investigate in timeline

* add investigat in timeline button

* fix failing tests

* add alerts status actions

* update unit test

* export alerts actions from hook

* add disable props

* add case action items

* clean up

* split alert status hook and hide add to case action

* add useHoseIsolationAction hook

* move out take action dropdown

* refeactor hooks to only manage one thing

* apply hooks to alerts table

* clean up

* fix unit tests

* replace euiCodeBlock

* take actions from case

* fetch ecs in flyout footer

* move fetch alert ecs to container

* add AddExceptionModalWrapperData interface

* fix cypress tests

* update snapshot for json view

* fix cypress test

* update AddEndpointExceptionComponent

* fix data retrieved from event details

* fix host isolation action

* use endpointAlertCheck

Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
  • Loading branch information
angorayc and XavierM committed Aug 5, 2021
1 parent c77de2a commit 784d431
Show file tree
Hide file tree
Showing 40 changed files with 1,721 additions and 711 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ export type TimelineExpandedEventType =
params?: {
eventId: string;
indexName: string;
refetch?: () => void;
};
}
| EmptyObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ describe('Alert details with unmapped fields', () => {
const length = elements.length;
cy.wrap(elements)
.eq(length - expectedUnmappedField.line)
.should('have.text', expectedUnmappedField.text);
.invoke('text')
.should('include', expectedUnmappedField.text);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ describe('CTI Enrichment', () => {
expectedEnrichment.forEach((enrichment) => {
cy.wrap(elements)
.eq(length - enrichment.line)
.should('have.text', enrichment.text);
.invoke('text')
.should('include', enrichment.text);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,9 @@ describe('Custom detection rules deletion and edition', () => {
goToManageAlertsDetectionRules();
waitForAlertsIndexToBeCreated();
createCustomRuleActivated(getNewRule(), 'rule1');
createCustomRuleActivated(getNewRule(), 'rule2');

createCustomRuleActivated(getNewOverrideRule(), 'rule3');
createCustomRuleActivated(getExistingRule(), 'rule4');
createCustomRuleActivated(getNewOverrideRule(), 'rule2');
createCustomRuleActivated(getExistingRule(), 'rule3');
reload();
});

Expand Down Expand Up @@ -295,7 +294,7 @@ describe('Custom detection rules deletion and edition', () => {
});
cy.get(SHOWING_RULES_TEXT).should(
'have.text',
`Showing ${expectedNumberOfRulesAfterDeletion} rules`
`Showing ${expectedNumberOfRulesAfterDeletion} rule`
);
cy.get(CUSTOM_RULES_BTN).should(
'have.text',
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/cypress/screens/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

export const ADD_EXCEPTION_BTN = '[data-test-subj="addExceptionButton"]';
export const ADD_EXCEPTION_BTN = '[data-test-subj="add-exception-menu-item"]';

export const ALERTS = '[data-test-subj="events-viewer-panel"] [data-test-subj="event"]';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export const ALERT_FLYOUT = '[data-test-subj="timeline:details-panel:flyout"]';

export const CELL_TEXT = '.euiText';

export const JSON_VIEW_WRAPPER = '[data-test-subj="jsonViewWrapper"]';

export const JSON_CONTENT = '[data-test-subj="jsonView"]';

export const JSON_LINES = '.ace_line';
export const JSON_LINES = '.euiCodeBlock__line';

export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import {
ENRICHMENT_COUNT_NOTIFICATION,
JSON_CONTENT,
JSON_VIEW_WRAPPER,
JSON_VIEW_TAB,
TABLE_TAB,
} from '../screens/alerts_details';
Expand All @@ -25,7 +25,7 @@ export const openThreatIndicatorDetails = () => {
};

export const scrollJsonViewToBottom = () => {
cy.get(JSON_CONTENT).click({ force: true });
cy.get(JSON_CONTENT).type('{pagedown}{pagedown}{pagedown}');
cy.get(JSON_CONTENT).should('be.visible');
cy.get(JSON_VIEW_WRAPPER).click({ force: true });
cy.get(JSON_VIEW_WRAPPER).type('{pagedown}{pagedown}{pagedown}');
cy.get(JSON_VIEW_WRAPPER).should('be.visible');
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

import React, { useCallback, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { SearchResponse } from 'elasticsearch';
import { isEmpty } from 'lodash';

import {
getCaseDetailsUrl,
Expand All @@ -18,18 +16,17 @@ import {
getRuleDetailsUrl,
useFormatUrl,
} from '../../../common/components/link_to';
import { Ecs } from '../../../../common/ecs';
import { Case, CaseViewRefreshPropInterface } from '../../../../../cases/common';
import { TimelineId } from '../../../../common/types/timeline';
import { SecurityPageName } from '../../../app/types';
import { KibanaServices, useKibana } from '../../../common/lib/kibana';
import { APP_ID, DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants';
import { useKibana } from '../../../common/lib/kibana';
import { APP_ID } from '../../../../common/constants';
import { timelineActions } from '../../../timelines/store/timeline';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { DetailsPanel } from '../../../timelines/components/side_panel';
import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action';
import { buildAlertsQuery, formatAlertToEcsSignal, useFetchAlertData } from './helpers';
import { useFetchAlertData } from './helpers';
import { SEND_ALERT_TO_TIMELINE } from './translations';
import { useInsertTimeline } from '../use_insert_timeline';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
Expand Down Expand Up @@ -70,39 +67,12 @@ const TimelineDetailsPanel = () => {
};

const InvestigateInTimelineActionComponent = (alertIds: string[]) => {
const fetchEcsAlertsData = async (fetchAlertIds?: string[]): Promise<Ecs[]> => {
if (isEmpty(fetchAlertIds)) {
return [];
}
const alertResponse = await KibanaServices.get().http.fetch<
SearchResponse<{ '@timestamp': string; [key: string]: unknown }>
>(DETECTION_ENGINE_QUERY_SIGNALS_URL, {
method: 'POST',
body: JSON.stringify(buildAlertsQuery(fetchAlertIds ?? [])),
});
return (
alertResponse?.hits.hits.reduce<Ecs[]>(
(acc, { _id, _index, _source }) => [
...acc,
{
...formatAlertToEcsSignal(_source as {}),
_id,
_index,
timestamp: _source['@timestamp'],
},
],
[]
) ?? []
);
};

return (
<InvestigateInTimelineAction
ariaLabel={SEND_ALERT_TO_TIMELINE}
alertIds={alertIds}
key="investigate-in-timeline"
ecsRowData={null}
fetchEcsAlertsData={fetchEcsAlertsData}
nonEcsRowData={[]}
/>
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ const EventDetailsComponent: React.FC<Props> = ({
content: (
<>
<EuiSpacer size="m" />
<TabContentWrapper>
<TabContentWrapper data-test-subj="jsonViewWrapper">
<JsonView data={data} />
</TabContentWrapper>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { EuiCodeEditor } from '@elastic/eui';
import { EuiCodeBlock } from '@elastic/eui';
import { set } from '@elastic/safer-lodash-set/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';
Expand All @@ -23,8 +23,6 @@ const EuiCodeEditorContainer = styled.div`
}
`;

const EDITOR_SET_OPTIONS = { fontSize: '12px' };

export const JsonView = React.memo<Props>(({ data }) => {
const value = useMemo(
() =>
Expand All @@ -38,15 +36,15 @@ export const JsonView = React.memo<Props>(({ data }) => {

return (
<EuiCodeEditorContainer>
<EuiCodeEditor
<EuiCodeBlock
language="json"
fontSize="m"
paddingSize="m"
isCopyable
data-test-subj="jsonView"
isReadOnly
mode="javascript"
setOptions={EDITOR_SET_OPTIONS}
value={value}
width="100%"
height="100%"
/>
>
{value}
</EuiCodeBlock>
</EuiCodeEditorContainer>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { EuiContextMenuItem, EuiText } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';

interface AddEndpointExceptionProps {
onClick: () => void;
disabled?: boolean;
}

const AddEndpointExceptionComponent: React.FC<AddEndpointExceptionProps> = ({
onClick,
disabled,
}) => {
return (
<EuiContextMenuItem
key="add-endpoint-exception-menu-item"
aria-label={i18n.ACTION_ADD_ENDPOINT_EXCEPTION}
data-test-subj="add-endpoint-exception-menu-item"
id="addEndpointException"
onClick={onClick}
disabled={disabled}
>
<EuiText size="m">{i18n.ACTION_ADD_ENDPOINT_EXCEPTION}</EuiText>
</EuiContextMenuItem>
);
};

export const AddEndpointException = React.memo(AddEndpointExceptionComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { EuiContextMenuItem, EuiText } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';

interface AddEventFilterProps {
onClick: () => void;
disabled?: boolean;
}

const AddEventFilterComponent: React.FC<AddEventFilterProps> = ({ onClick, disabled }) => {
return (
<EuiContextMenuItem
key="add-event-filter-menu-item"
aria-label={i18n.ACTION_ADD_EVENT_FILTER}
data-test-subj="add-event-filter-menu-item"
id="addEventFilter"
onClick={onClick}
disabled={disabled}
>
<EuiText data-test-subj="addEventFilterButton" size="m">
{i18n.ACTION_ADD_EVENT_FILTER}
</EuiText>
</EuiContextMenuItem>
);
};

export const AddEventFilter = React.memo(AddEventFilterComponent);
Loading

0 comments on commit 784d431

Please sign in to comment.