mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Analyzer] Enable process title to open event preview (#210118)
## Summary Updated process event title to be a link, opens a event preview of that process event #### `enableVisualizationsInFlyout` advanced setting is on: Link is enabled https://github.com/user-attachments/assets/a7d1992a-0b7f-436c-9137-c6626077661b #### `enableVisualizationsInFlyout` advanced setting is off: Link is not enabled (no change)  ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
7dd40580bd
commit
8c05633cb8
3 changed files with 118 additions and 5 deletions
|
@ -32,7 +32,13 @@ export const PanelRouter = memo(function ({
|
|||
selectors.panelViewAndParameters(state.analyzer[id])
|
||||
);
|
||||
if (params.panelView === 'nodeDetail') {
|
||||
return <NodeDetail id={id} nodeID={params.panelParameters.nodeID} />;
|
||||
return (
|
||||
<NodeDetail
|
||||
id={id}
|
||||
nodeID={params.panelParameters.nodeID}
|
||||
nodeEventOnClick={nodeEventOnClick}
|
||||
/>
|
||||
);
|
||||
} else if (params.panelView === 'nodeEvents') {
|
||||
return <NodeEvents id={id} nodeID={params.panelParameters.nodeID} />;
|
||||
} else if (params.panelView === 'nodeEventsInCategory') {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 * as redux from 'react-redux';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { NodeDetailView } from './node_detail';
|
||||
import { useCubeAssets } from '../use_cube_assets';
|
||||
import { useLinkProps } from '../use_link_props';
|
||||
|
||||
const mockUseCubeAssets = useCubeAssets as jest.Mock;
|
||||
jest.mock('../use_cube_assets');
|
||||
|
||||
const mockUseLinkProps = useLinkProps as jest.Mock;
|
||||
jest.mock('../use_link_props');
|
||||
|
||||
const processEvent = {
|
||||
_id: 'test_id',
|
||||
_index: '_index',
|
||||
'@timestamp': 1726589803115,
|
||||
event: {
|
||||
id: 'event id',
|
||||
kind: 'event',
|
||||
category: 'process',
|
||||
},
|
||||
};
|
||||
|
||||
describe('<NodeDetailView />', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseLinkProps.mockReturnValue({ href: '#', onClick: jest.fn() });
|
||||
mockUseCubeAssets.mockReturnValue({
|
||||
descriptionText: 'test process',
|
||||
});
|
||||
jest.spyOn(redux, 'useSelector').mockReturnValueOnce('success');
|
||||
jest.spyOn(redux, 'useSelector').mockReturnValueOnce(1);
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<NodeDetailView id="test" nodeID="test" processEvent={processEvent} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('resolver:panel:node-detail')).toBeInTheDocument();
|
||||
expect(getByTestId('resolver:node-detail:title')).toBeInTheDocument();
|
||||
expect(getByTestId('resolver:node-detail:node-events-link')).toBeInTheDocument();
|
||||
expect(getByTestId('resolver:node-detail')).toBeInTheDocument();
|
||||
expect(queryByTestId('resolver:node-detail:title-link')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render process name as link when nodeEventOnClick is available', () => {
|
||||
const nodeEventOnClick = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<NodeDetailView
|
||||
id="test"
|
||||
nodeID="test"
|
||||
nodeEventOnClick={nodeEventOnClick}
|
||||
processEvent={processEvent}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId('resolver:node-detail:title-link')).toBeInTheDocument();
|
||||
getByTestId('resolver:node-detail:title-link').click();
|
||||
expect(nodeEventOnClick).toBeCalledWith({
|
||||
documentId: 'test_id',
|
||||
indexName: '_index',
|
||||
scopeId: 'test',
|
||||
isAlert: false,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import styled from 'styled-components';
|
||||
import { EventKind } from '../../../flyout/document_details/shared/constants/event_kinds';
|
||||
import { StyledTitle } from './styles';
|
||||
import * as selectors from '../../store/selectors';
|
||||
import * as eventModel from '../../../../common/endpoint/models/event';
|
||||
|
@ -41,6 +42,7 @@ import { useLinkProps } from '../use_link_props';
|
|||
import { useFormattedDate } from './use_formatted_date';
|
||||
import { PanelContentError } from './panel_content_error';
|
||||
import type { State } from '../../../common/store/types';
|
||||
import type { NodeEventOnClick } from './node_events_of_type';
|
||||
|
||||
const StyledCubeForProcess = styled(CubeForProcess)`
|
||||
position: relative;
|
||||
|
@ -51,7 +53,15 @@ const nodeDetailError = i18n.translate('xpack.securitySolution.resolver.panel.no
|
|||
});
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const NodeDetail = memo(function ({ id, nodeID }: { id: string; nodeID: string }) {
|
||||
export const NodeDetail = memo(function ({
|
||||
id,
|
||||
nodeID,
|
||||
nodeEventOnClick,
|
||||
}: {
|
||||
id: string;
|
||||
nodeID: string;
|
||||
nodeEventOnClick?: NodeEventOnClick;
|
||||
}) {
|
||||
const processEvent = useSelector((state: State) =>
|
||||
nodeDataModel.firstEvent(selectors.nodeDataForID(state.analyzer[id])(nodeID))
|
||||
);
|
||||
|
@ -62,7 +72,12 @@ export const NodeDetail = memo(function ({ id, nodeID }: { id: string; nodeID: s
|
|||
return nodeStatus === 'loading' ? (
|
||||
<PanelLoading id={id} />
|
||||
) : processEvent ? (
|
||||
<NodeDetailView id={id} nodeID={nodeID} processEvent={processEvent} />
|
||||
<NodeDetailView
|
||||
id={id}
|
||||
nodeID={nodeID}
|
||||
processEvent={processEvent}
|
||||
nodeEventOnClick={nodeEventOnClick}
|
||||
/>
|
||||
) : (
|
||||
<PanelContentError id={id} translatedErrorMessage={nodeDetailError} />
|
||||
);
|
||||
|
@ -78,14 +93,16 @@ export interface NodeDetailsTableView {
|
|||
* Created, PID, User/Domain, etc.
|
||||
*/
|
||||
// eslint-disable-next-line react/display-name
|
||||
const NodeDetailView = memo(function ({
|
||||
export const NodeDetailView = memo(function ({
|
||||
id,
|
||||
processEvent,
|
||||
nodeID,
|
||||
nodeEventOnClick,
|
||||
}: {
|
||||
id: string;
|
||||
processEvent: SafeResolverEvent;
|
||||
nodeID: string;
|
||||
nodeEventOnClick?: NodeEventOnClick;
|
||||
}) {
|
||||
const processName = eventModel.processNameSafeVersion(processEvent);
|
||||
const nodeState = useSelector((state: State) =>
|
||||
|
@ -96,6 +113,9 @@ const NodeDetailView = memo(function ({
|
|||
});
|
||||
const eventTime = eventModel.eventTimestamp(processEvent);
|
||||
const dateTime = useFormattedDate(eventTime);
|
||||
const isAlert = eventModel.eventKind(processEvent)[0] === EventKind.signal;
|
||||
const documentId = eventModel.documentID(processEvent);
|
||||
const indexName = eventModel.indexName(processEvent);
|
||||
|
||||
const processInfoEntry: NodeDetailsTableView[] = useMemo(() => {
|
||||
const createdEntry = {
|
||||
|
@ -278,7 +298,16 @@ const NodeDetailView = memo(function ({
|
|||
state={nodeState}
|
||||
/>
|
||||
<span data-test-subj="resolver:node-detail:title">
|
||||
<GeneratedText>{processName}</GeneratedText>
|
||||
{nodeEventOnClick ? (
|
||||
<EuiLink
|
||||
data-test-subj="resolver:node-detail:title-link"
|
||||
onClick={nodeEventOnClick({ documentId, indexName, scopeId: id, isAlert })}
|
||||
>
|
||||
<GeneratedText>{processName}</GeneratedText>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<GeneratedText>{processName}</GeneratedText>
|
||||
)}
|
||||
</span>
|
||||
</StyledTitle>
|
||||
</EuiTitle>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue