Skip to content

Commit

Permalink
[Security Solution][Alert Flyout] Enable network preview and previews…
Browse files Browse the repository at this point in the history
… in table tab (elastic#190560)

## Summary

This PR is a refactor of preview links in document details flyout:

1) Replace all the `EuiLink` component with a shared `PreviewLink` that
renders a link based on field
2) Added IP (network) flyout previews through out document details
flyout
3) Added preview capabilities for fields in table tab 

**Network previews**

<img width="1420" alt="image"
src="https://github.com/user-attachments/assets/8dd52e1a-e48a-44cd-9eb3-bf34b067bf93">



**After**


https://github.com/user-attachments/assets/451292f6-3056-4362-87e4-e8170f0bda3f



**Exceptions**
IP addresses in entity details section are not yet worked on, as it is
owned by the Explore team and therefore has impacts outside of alerts
flyout.

<img width="919" alt="image"
src="https://github.com/user-attachments/assets/4d5fb2be-b132-4db0-ae65-32caa8e0d49f">

**How to test**
- Using the normal resolver script should generate host, user and ip
data for testing.
- To test ip's in highlighted fields, user should create a custom rule
and add some ip fields to highlighted fields

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
christineweng and kibanamachine authored Sep 5, 2024
1 parent c213832 commit 57ae12c
Show file tree
Hide file tree
Showing 33 changed files with 729 additions and 478 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID,
HOST_DETAILS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID,
} from './test_ids';
import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-common';
import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score';
Expand All @@ -33,6 +34,7 @@ import { HostPreviewPanelKey } from '../../../entity_details/host_right';
import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview';
import { UserPreviewPanelKey } from '../../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview';
import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../../network_details';

jest.mock('@kbn/expandable-flyout');

Expand Down Expand Up @@ -164,7 +166,7 @@ describe('<HostDetails />', () => {
expect(queryByTestId(HOST_DETAILS_LINK_TEST_ID)).not.toBeInTheDocument();
});

it('should render host name as clicable link when feature flag is true', () => {
it('should render host name as clicable link when preview is not disabled', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
const { getByTestId } = renderHostDetails(mockContextValue);
expect(getByTestId(HOST_DETAILS_LINK_TEST_ID)).toBeInTheDocument();
Expand Down Expand Up @@ -268,7 +270,7 @@ describe('<HostDetails />', () => {
);
});

it('should render user name as clicable link when feature flag is true', () => {
it('should render user name as clicable link when preview is not disabled', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
const { getAllByTestId } = renderHostDetails(mockContextValue);
expect(getAllByTestId(HOST_DETAILS_RELATED_USERS_LINK_TEST_ID).length).toBe(1);
Expand All @@ -282,6 +284,16 @@ describe('<HostDetails />', () => {
banner: USER_PREVIEW_BANNER,
},
});

getAllByTestId(HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID)[0].click();
expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({
id: NetworkPanelKey,
params: {
ip: '100.XXX.XXX',
flowTarget: 'source',
banner: NETWORK_PREVIEW_BANNER,
},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
EuiToolTip,
EuiIcon,
EuiPanel,
EuiLink,
} from '@elastic/eui';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
Expand All @@ -36,7 +35,7 @@ import { RiskScoreEntity } from '../../../../../common/search_strategy';
import { RiskScoreLevel } from '../../../../entity_analytics/components/severity/common';
import { DefaultFieldRenderer } from '../../../../timelines/components/field_renderers/default_renderer';
import { InputsModelId } from '../../../../common/store/inputs/constants';
import { CellActions } from './cell_actions';
import { CellActions } from '../../shared/components/cell_actions';
import { useGlobalTime } from '../../../../common/containers/use_global_time';
import { useSourcererDataView } from '../../../../sourcerer/containers';
import { manageQuery } from '../../../../common/components/page/manage_query';
Expand All @@ -51,14 +50,18 @@ import {
HOST_DETAILS_TEST_ID,
HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID,
HOST_DETAILS_RELATED_USERS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID,
} from './test_ids';
import {
USER_NAME_FIELD_NAME,
HOST_IP_FIELD_NAME,
} from '../../../../timelines/components/timeline/body/renderers/constants';
import { useKibana } from '../../../../common/lib/kibana';
import { ENTITY_RISK_LEVEL } from '../../../../entity_analytics/components/risk_score/translations';
import { useHasSecurityCapability } from '../../../../helper_hooks';
import { PreviewLink } from '../../../shared/components/preview_link';
import { HostPreviewPanelKey } from '../../../entity_details/host_right';
import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview';
import { UserPreviewPanelKey } from '../../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview';
import type { NarrowDateRange } from '../../../../common/components/ml/types';

const HOST_DETAILS_ID = 'entities-hosts-details';
Expand Down Expand Up @@ -129,24 +132,6 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp, s
});
}, [openPreviewPanel, hostName, scopeId, telemetry]);

const openUserPreview = useCallback(
(userName: string) => {
openPreviewPanel({
id: UserPreviewPanelKey,
params: {
userName,
scopeId,
banner: USER_PREVIEW_BANNER,
},
});
telemetry.reportDetailsFlyoutOpened({
location: scopeId,
panel: 'preview',
});
},
[openPreviewPanel, scopeId, telemetry]
);

const [isHostLoading, { inspect, hostDetails, refetch }] = useHostDetails({
id: hostDetailsQueryId,
startDate: from,
Expand Down Expand Up @@ -181,14 +166,14 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp, s
),
render: (user: string) => (
<EuiText grow={false} size="xs">
<CellActions field={'user.name'} value={user}>
<CellActions field={USER_NAME_FIELD_NAME} value={user}>
{isPreviewEnabled ? (
<EuiLink
<PreviewLink
field={USER_NAME_FIELD_NAME}
value={user}
scopeId={scopeId}
data-test-subj={HOST_DETAILS_RELATED_USERS_LINK_TEST_ID}
onClick={() => openUserPreview(user)}
>
{user}
</EuiLink>
/>
) : (
<>{user}</>
)}
Expand All @@ -208,10 +193,23 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp, s
return (
<DefaultFieldRenderer
rowItems={ips}
attrName={'host.ip'}
attrName={HOST_IP_FIELD_NAME}
idPrefix={''}
isDraggable={false}
render={(ip) => (ip != null ? <NetworkDetailsLink ip={ip} /> : getEmptyTagValue())}
render={(ip) =>
ip == null ? (
getEmptyTagValue()
) : isPreviewEnabled ? (
<PreviewLink
field={HOST_IP_FIELD_NAME}
value={ip}
scopeId={scopeId}
data-test-subj={HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID}
/>
) : (
<NetworkDetailsLink ip={ip} />
)
}
scopeId={scopeId}
/>
);
Expand All @@ -235,7 +233,7 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp, s
]
: []),
],
[isEntityAnalyticsAuthorized, scopeId, isPreviewEnabled, openUserPreview]
[isEntityAnalyticsAuthorized, scopeId, isPreviewEnabled]
);

const relatedUsersCount = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {
PREVALENCE_DETAILS_UPSELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID,
} from './test_ids';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
Expand All @@ -32,9 +31,21 @@ import { HostPreviewPanelKey } from '../../../entity_details/host_right';
import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview';
import { UserPreviewPanelKey } from '../../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview';
import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock';

jest.mock('@kbn/expandable-flyout');

const mockedTelemetry = createTelemetryServiceMock();
jest.mock('../../../../common/lib/kibana', () => {
return {
useKibana: () => ({
services: {
telemetry: mockedTelemetry,
},
}),
};
});

jest.mock('../../../../common/hooks/use_experimental_features');
const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock;

Expand Down Expand Up @@ -151,19 +162,19 @@ describe('PrevalenceDetails', () => {
).toBeGreaterThan(1);
expect(queryByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).not.toBeInTheDocument();
expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
expect(queryByTestId(PREVALENCE_DETAILS_TABLE_HOST_LINK_CELL_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(PREVALENCE_DETAILS_TABLE_USER_LINK_CELL_TEST_ID)).not.toBeInTheDocument();
expect(
queryByTestId(PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID)
).not.toBeInTheDocument();
});

it('should render host and user name as clickable link if feature flag is true', () => {
it('should render host and user name as clickable link if preview is enabled', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
(usePrevalence as jest.Mock).mockReturnValue(mockPrevelanceReturnValue);

const { getByTestId } = renderPrevalenceDetails();
expect(getByTestId(PREVALENCE_DETAILS_TABLE_HOST_LINK_CELL_TEST_ID)).toBeInTheDocument();
expect(getByTestId(PREVALENCE_DETAILS_TABLE_USER_LINK_CELL_TEST_ID)).toBeInTheDocument();
const { getAllByTestId } = renderPrevalenceDetails();
expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID)).toHaveLength(2);

getByTestId(PREVALENCE_DETAILS_TABLE_HOST_LINK_CELL_TEST_ID).click();
getAllByTestId(PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID)[0].click();
expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({
id: HostPreviewPanelKey,
params: {
Expand All @@ -173,7 +184,7 @@ describe('PrevalenceDetails', () => {
},
});

getByTestId(PREVALENCE_DETAILS_TABLE_USER_LINK_CELL_TEST_ID).click();
getAllByTestId(PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID)[1].click();
expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({
id: UserPreviewPanelKey,
params: {
Expand Down
Loading

0 comments on commit 57ae12c

Please sign in to comment.