-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security team: AWP] Session view: Alert details tab (#127500)
* alerts tab work. list view done * View mode toggle + group view implemented * tests written * clean up * addressed @opauloh comments * fixed weird bug due to importing assests from a test into its component * empty state added for alerts tab * react-query caching keys updated to include sessionEntityId * rule_registry added as a dependency in order to use AlertsClient in alerts_route.ts * fixed build/test errors due to merge. events route now orders by process.start then @timestamp * plumbing for the alert details tie in done. * removed rule_registry ecs mappings. kqualters PR will add this. * alerts index merge conflict fix Co-authored-by: mitodrummer <karlgodard@elastic.co>
- Loading branch information
1 parent
d48b82a
commit c55bb91
Showing
32 changed files
with
1,555 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
x-pack/plugins/session_view/public/components/detail_panel_alert_actions/index.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* 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 React from 'react'; | ||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; | ||
import { | ||
DetailPanelAlertActions, | ||
BUTTON_TEST_ID, | ||
SHOW_DETAILS_TEST_ID, | ||
JUMP_TO_PROCESS_TEST_ID, | ||
} from './index'; | ||
import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { ProcessImpl } from '../process_tree/hooks'; | ||
|
||
describe('DetailPanelAlertActions component', () => { | ||
let render: () => ReturnType<AppContextTestRender['render']>; | ||
let renderResult: ReturnType<typeof render>; | ||
let mockedContext: AppContextTestRender; | ||
let mockShowAlertDetails = jest.fn((uuid) => uuid); | ||
let mockOnProcessSelected = jest.fn((process) => process); | ||
|
||
beforeEach(() => { | ||
mockedContext = createAppRootMockRenderer(); | ||
mockShowAlertDetails = jest.fn((uuid) => uuid); | ||
mockOnProcessSelected = jest.fn((process) => process); | ||
}); | ||
|
||
describe('When DetailPanelAlertActions is mounted', () => { | ||
it('renders a popover when button is clicked', async () => { | ||
const mockEvent = mockAlerts[0]; | ||
|
||
renderResult = mockedContext.render( | ||
<DetailPanelAlertActions | ||
event={mockEvent} | ||
onProcessSelected={mockOnProcessSelected} | ||
onShowAlertDetails={mockShowAlertDetails} | ||
/> | ||
); | ||
|
||
userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID)); | ||
expect(renderResult.queryByTestId(SHOW_DETAILS_TEST_ID)).toBeTruthy(); | ||
expect(renderResult.queryByTestId(JUMP_TO_PROCESS_TEST_ID)).toBeTruthy(); | ||
expect(mockShowAlertDetails.mock.calls.length).toBe(0); | ||
expect(mockOnProcessSelected.mock.calls.length).toBe(0); | ||
}); | ||
|
||
it('calls alert flyout callback when View details clicked', async () => { | ||
const mockEvent = mockAlerts[0]; | ||
|
||
renderResult = mockedContext.render( | ||
<DetailPanelAlertActions | ||
event={mockEvent} | ||
onProcessSelected={mockOnProcessSelected} | ||
onShowAlertDetails={mockShowAlertDetails} | ||
/> | ||
); | ||
|
||
userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID)); | ||
userEvent.click(renderResult.getByTestId(SHOW_DETAILS_TEST_ID)); | ||
expect(mockShowAlertDetails.mock.calls.length).toBe(1); | ||
expect(mockShowAlertDetails.mock.results[0].value).toBe(mockEvent.kibana?.alert.uuid); | ||
expect(mockOnProcessSelected.mock.calls.length).toBe(0); | ||
}); | ||
|
||
it('calls onProcessSelected when Jump to process clicked', async () => { | ||
const mockEvent = mockAlerts[0]; | ||
|
||
renderResult = mockedContext.render( | ||
<DetailPanelAlertActions | ||
event={mockEvent} | ||
onProcessSelected={mockOnProcessSelected} | ||
onShowAlertDetails={mockShowAlertDetails} | ||
/> | ||
); | ||
|
||
userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID)); | ||
userEvent.click(renderResult.getByTestId(JUMP_TO_PROCESS_TEST_ID)); | ||
expect(mockOnProcessSelected.mock.calls.length).toBe(1); | ||
expect(mockOnProcessSelected.mock.results[0].value).toBeInstanceOf(ProcessImpl); | ||
expect(mockShowAlertDetails.mock.calls.length).toBe(0); | ||
}); | ||
}); | ||
}); |
105 changes: 105 additions & 0 deletions
105
x-pack/plugins/session_view/public/components/detail_panel_alert_actions/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* 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 React, { useState, useCallback } from 'react'; | ||
import { i18n } from '@kbn/i18n'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import { EuiPopover, EuiContextMenuPanel, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui'; | ||
import { Process, ProcessEvent } from '../../../common/types/process_tree'; | ||
import { ProcessImpl } from '../process_tree/hooks'; | ||
|
||
export const BUTTON_TEST_ID = 'sessionView:detailPanelAlertActionsBtn'; | ||
export const SHOW_DETAILS_TEST_ID = 'sessionView:detailPanelAlertActionShowDetails'; | ||
export const JUMP_TO_PROCESS_TEST_ID = 'sessionView:detailPanelAlertActionJumpToProcess'; | ||
|
||
interface DetailPanelAlertActionsDeps { | ||
event: ProcessEvent; | ||
onShowAlertDetails: (alertId: string) => void; | ||
onProcessSelected: (process: Process) => void; | ||
} | ||
|
||
/** | ||
* Detail panel alert context menu actions | ||
*/ | ||
export const DetailPanelAlertActions = ({ | ||
event, | ||
onShowAlertDetails, | ||
onProcessSelected, | ||
}: DetailPanelAlertActionsDeps) => { | ||
const [isPopoverOpen, setPopover] = useState(false); | ||
|
||
const onClosePopover = useCallback(() => { | ||
setPopover(false); | ||
}, []); | ||
|
||
const onToggleMenu = useCallback(() => { | ||
setPopover(!isPopoverOpen); | ||
}, [isPopoverOpen]); | ||
|
||
const onJumpToAlert = useCallback(() => { | ||
const process = new ProcessImpl(event.process.entity_id); | ||
process.addEvent(event); | ||
|
||
onProcessSelected(process); | ||
setPopover(false); | ||
}, [event, onProcessSelected]); | ||
|
||
const onShowDetails = useCallback(() => { | ||
if (event.kibana) { | ||
onShowAlertDetails(event.kibana.alert.uuid); | ||
setPopover(false); | ||
} | ||
}, [event, onShowAlertDetails]); | ||
|
||
if (!event.kibana) { | ||
return null; | ||
} | ||
|
||
const { uuid } = event.kibana.alert; | ||
|
||
const menuItems = [ | ||
<EuiContextMenuItem key="details" data-test-subj={SHOW_DETAILS_TEST_ID} onClick={onShowDetails}> | ||
<FormattedMessage | ||
id="xpack.sessionView.detailPanelAlertListItem.showDetailsAction" | ||
defaultMessage="View alert details" | ||
/> | ||
</EuiContextMenuItem>, | ||
<EuiContextMenuItem | ||
key="jumpTo" | ||
data-test-subj={JUMP_TO_PROCESS_TEST_ID} | ||
onClick={onJumpToAlert} | ||
> | ||
<FormattedMessage | ||
id="xpack.sessionView.detailPanelAlertListItem.jumpToAlert" | ||
defaultMessage="Jump to alerted process" | ||
/> | ||
</EuiContextMenuItem>, | ||
]; | ||
|
||
return ( | ||
<EuiPopover | ||
id={uuid} | ||
button={ | ||
<EuiButtonIcon | ||
display="empty" | ||
size="s" | ||
iconType="boxesHorizontal" | ||
aria-label={i18n.translate('xpack.sessionView.detailPanelAlertListItem.moreButton', { | ||
defaultMessage: 'More', | ||
})} | ||
data-test-subj={BUTTON_TEST_ID} | ||
onClick={onToggleMenu} | ||
/> | ||
} | ||
isOpen={isPopoverOpen} | ||
closePopover={onClosePopover} | ||
panelPaddingSize="none" | ||
anchorPosition="leftCenter" | ||
> | ||
<EuiContextMenuPanel size="s" items={menuItems} /> | ||
</EuiPopover> | ||
); | ||
}; |
107 changes: 107 additions & 0 deletions
107
x-pack/plugins/session_view/public/components/detail_panel_alert_actions/styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* | ||
* 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 { useMemo } from 'react'; | ||
import { useEuiTheme, transparentize } from '@elastic/eui'; | ||
import { CSSObject, css } from '@emotion/react'; | ||
|
||
interface StylesDeps { | ||
minimal?: boolean; | ||
isInvestigated?: boolean; | ||
} | ||
|
||
export const useStyles = ({ minimal = false, isInvestigated = false }: StylesDeps) => { | ||
const { euiTheme } = useEuiTheme(); | ||
|
||
const cached = useMemo(() => { | ||
const { colors, font, size, border } = euiTheme; | ||
|
||
const dangerBorder = transparentize(colors.danger, 0.2); | ||
const dangerBackground = transparentize(colors.danger, 0.08); | ||
const borderThickness = border.width.thin; | ||
const mediumPadding = size.m; | ||
|
||
let alertTitleColor = colors.text; | ||
let borderColor = colors.lightShade; | ||
|
||
if (isInvestigated) { | ||
alertTitleColor = colors.primaryText; | ||
borderColor = dangerBorder; | ||
} | ||
|
||
const alertItem = css` | ||
border: ${borderThickness} solid ${borderColor}; | ||
padding: ${mediumPadding}; | ||
border-radius: ${border.radius.medium}; | ||
margin: 0 ${mediumPadding} ${mediumPadding} ${mediumPadding}; | ||
background-color: ${colors.emptyShade}; | ||
& .euiAccordion__buttonContent { | ||
width: 100%; | ||
} | ||
& .euiAccordion__button { | ||
min-width: 0; | ||
width: calc(100% - ${size.l}); | ||
} | ||
& .euiAccordion__childWrapper { | ||
overflow: visible; | ||
} | ||
`; | ||
|
||
const alertTitle: CSSObject = { | ||
display: minimal ? 'none' : 'initial', | ||
color: alertTitleColor, | ||
fontWeight: font.weight.semiBold, | ||
textOverflow: 'ellipsis', | ||
overflow: 'hidden', | ||
whiteSpace: 'nowrap', | ||
}; | ||
|
||
const alertIcon: CSSObject = { | ||
marginRight: size.s, | ||
}; | ||
|
||
const alertAccordionButton: CSSObject = { | ||
width: `calc(100% - ${size.l})`, | ||
minWidth: 0, | ||
}; | ||
|
||
const processPanel: CSSObject = { | ||
border: `${borderThickness} solid ${colors.lightShade}`, | ||
fontFamily: font.familyCode, | ||
marginTop: mediumPadding, | ||
padding: `${size.xs} ${size.s}`, | ||
}; | ||
|
||
const investigatedLabel: CSSObject = { | ||
position: 'relative', | ||
zIndex: 1, | ||
bottom: `-${mediumPadding}`, | ||
left: `-${mediumPadding}`, | ||
width: `calc(100% + ${mediumPadding} * 2)`, | ||
borderTop: `${borderThickness} solid ${dangerBorder}`, | ||
borderBottomLeftRadius: border.radius.medium, | ||
borderBottomRightRadius: border.radius.medium, | ||
backgroundColor: dangerBackground, | ||
textAlign: 'center', | ||
}; | ||
|
||
return { | ||
alertItem, | ||
alertTitle, | ||
alertIcon, | ||
alertAccordionButton, | ||
processPanel, | ||
investigatedLabel, | ||
}; | ||
}, [euiTheme, isInvestigated, minimal]); | ||
|
||
return cached; | ||
}; |
Oops, something went wrong.